/*************************************************************************\ * Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS Base is distributed subject to a Software License Agreement found * in the file LICENSE that is included with this distribution. \*************************************************************************/ /* msi - macro substitutions and include */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_BUFFER_SIZE 4096 #define MAX_DEPS 1024 #if 0 /* Debug Tracing */ int din = 0; #define ENTER fprintf(stderr, "%*sEntering %s\n", 2*din++, "", __FUNCTION__) #define STEP(s) fprintf(stderr, "%*s%s: %s\n", 2*din, "", __FUNCTION__, s) #define STEPS(s, v) fprintf(stderr, "%*s%s: %s '%s'\n", 2*din, "", __FUNCTION__, s, v) #define STEPD(s, v) fprintf(stderr, "%*s%s: %s %d\n", 2*din, "", __FUNCTION__, s, v) #define EXIT fprintf(stderr, "%*s%s: Returning\n", 2*din--, "", __FUNCTION__) #define EXITD(r) fprintf(stderr, "%*s%s: Returning %d\n", 2*din--, "", __FUNCTION__, r) #define EXITS(r) fprintf(stderr, "%*s%s: Returning '%s'\n", 2*din--, "", __FUNCTION__, r) #else #define ENTER #define STEP(s) #define STEPS(s, v) #define STEPD(s, v) #define EXIT #define EXITD(r) #define EXITS(r) #endif /* Module to read the template files */ typedef struct inputData inputData; static void inputConstruct(inputData **ppvt); static void inputDestruct(inputData * const pvt); static void inputAddPath(inputData * const pvt, const char * const pval); static void inputBegin(inputData * const pvt, const char * const fileName); static char *inputNextLine(inputData * const pvt); static void inputNewIncludeFile(inputData * const pvt, const char * const name); static void inputErrPrint(const inputData * const pvt); /* Module to read the substitution file */ typedef struct subInfo subInfo; static void substituteOpen(subInfo **ppvt, const std::string& substitutionName); static void substituteDestruct(subInfo * const pvt); static bool substituteGetNextSet(subInfo * const pvt, char **filename); static bool substituteGetGlobalSet(subInfo * const pvt); static const char *substituteGetReplacements(subInfo * const pvt); static const char *substituteGetGlobalReplacements(subInfo * const pvt); /* Forward references to local routines */ static void usageExit(const int status); static void abortExit(const int status); static void addMacroReplacements(MAC_HANDLE * const macPvt, const char * const pval); static void makeSubstitutions(inputData * const inputPvt, MAC_HANDLE * const macPvt, const char * const templateName); /*Global variables */ static int opt_V = 0; static bool opt_D = false; static char *outFile = 0; static int numDeps = 0, depHashes[MAX_DEPS]; int main(int argc,char **argv) { inputData *inputPvt; MAC_HANDLE *macPvt; char *pval; std::string substitutionName; char *templateName = 0; bool localScope = true; inputConstruct(&inputPvt); macCreateHandle(&macPvt, 0); while ((argc > 1) && (argv[1][0] == '-')) { int narg = (strlen(argv[1]) == 2) ? 2 : 1; pval = (narg == 1) ? (argv[1] + 2) : argv[2]; if (strncmp(argv[1], "-I", 2) == 0) { inputAddPath(inputPvt, pval); } else if (strcmp(argv[1], "-D") == 0) { opt_D = true; narg = 1; /* no argument for this option */ } else if(strncmp(argv[1], "-o", 2) == 0) { outFile = epicsStrDup(pval); } else if(strncmp(argv[1], "-M", 2) == 0) { addMacroReplacements(macPvt, pval); } else if(strncmp(argv[1], "-S", 2) == 0) { substitutionName = pval; } else if (strcmp(argv[1], "-V") == 0) { opt_V = 1; narg = 1; /* no argument for this option */ } else if (strcmp(argv[1], "-g") == 0) { localScope = false; narg = 1; /* no argument for this option */ } else if (strcmp(argv[1], "-h") == 0) { usageExit(0); } else { fprintf(stderr, "msi: Bad argument \"%s\"\n", argv[1]); usageExit(1); } argc -= narg; for (int i = 1; i < argc; i++) argv[i] = argv[i + narg]; } if (!opt_V) macSuppressWarning(macPvt, 1); if (argc > 2) { fprintf(stderr, "msi: Too many arguments\n"); usageExit(1); } if (opt_D) { if (!outFile) { fprintf(stderr, "msi: Option -D requires -o for Makefile target\n"); exit(1); } printf("%s:", outFile); } else if (outFile && freopen(outFile, "w", stdout) == NULL) { fprintf(stderr, "msi: Can't open %s for writing: %s\n", outFile, strerror(errno)); exit(1); } if (argc == 2) templateName = epicsStrDup(argv[1]); if (substitutionName.empty()) { STEP("Single template+substitutions file"); makeSubstitutions(inputPvt, macPvt, templateName); } else { subInfo *substitutePvt; char *filename = 0; bool isGlobal, isFile; STEPS("Substitutions from file", substitutionName.c_str()); substituteOpen(&substitutePvt, substitutionName); do { isGlobal = substituteGetGlobalSet(substitutePvt); if (isGlobal) { STEP("Handling global macros"); const char *macStr = substituteGetGlobalReplacements(substitutePvt); if (macStr) addMacroReplacements(macPvt, macStr); } else if ((isFile = substituteGetNextSet(substitutePvt, &filename))) { if (templateName) filename = templateName; if (!filename) { fprintf(stderr, "msi: No template file\n"); usageExit(1); } STEPS("Handling template file", filename); const char *macStr; while ((macStr = substituteGetReplacements(substitutePvt))) { if (localScope) macPushScope(macPvt); addMacroReplacements(macPvt, macStr); makeSubstitutions(inputPvt, macPvt, filename); if (localScope) macPopScope(macPvt); } } } while (isGlobal || isFile); substituteDestruct(substitutePvt); } macDeleteHandle(macPvt); inputDestruct(inputPvt); if (opt_D) { printf("\n"); } fflush(stdout); free(templateName); return opt_V & 2; } void usageExit(const int status) { fprintf(stderr, "Usage: msi [options] [template]\n" " stdin is used if neither template nor substitution file is given\n" " options:\n" " -h Print this help message\n" " -D Output file dependencies, not substitutions\n" " -V Undefined macros generate an error\n" " -g All macros have global scope\n" " -o Send output to \n" " -I Add to include file search path\n" " -M Add to (global) macro definitions\n" " ( takes the form VAR=VALUE,...)\n" " -S Expand the substitutions in FILE\n"); exit(status); } void abortExit(const int status) { if (outFile) { fclose(stdout); unlink(outFile); } exit(status); } static void addMacroReplacements(MAC_HANDLE * const macPvt, const char * const pval) { char **pairs; long status; status = macParseDefns(macPvt, pval, &pairs); if (status == -1) { fprintf(stderr, "msi: Error from macParseDefns\n"); usageExit(1); } if (status) { status = macInstallMacros(macPvt, pairs); if (!status) { fprintf(stderr, "Error from macInstallMacros\n"); usageExit(1); } free(pairs); } } typedef enum {cmdInclude,cmdSubstitute} cmdType; static const char *cmdNames[] = {"include","substitute"}; static void makeSubstitutions(inputData * const inputPvt, MAC_HANDLE * const macPvt, const char * const templateName) { char *input; static char buffer[MAX_BUFFER_SIZE]; int n; ENTER; inputBegin(inputPvt, templateName); while ((input = inputNextLine(inputPvt))) { int expand=1; char *p; char *command = 0; p = input; /*skip whitespace at beginning of line*/ while (*p && (isspace((int) *p))) ++p; /*Look for i or s */ if (*p && (*p=='i' || *p=='s')) command = p; if (command) { char *pstart; char *pend; int cmdind=-1; int i; for (i = 0; i < NELEMENTS(cmdNames); i++) { if (strstr(command, cmdNames[i])) { cmdind = i; } } if (cmdind < 0) goto endcmd; p = command + strlen(cmdNames[cmdind]); /*skip whitespace after command*/ while (*p && (isspace((int) *p))) ++p; /*Next character must be quote*/ if ((*p == 0) || (*p != '"')) goto endcmd; pstart = ++p; /*Look for end quote*/ while (*p && (*p != '"')) { /*allow escape for embeded quote*/ if ((p[0] == '\\') && p[1] == '"') { p += 2; continue; } else { if (*p == '"') break; } ++p; } pend = p; if (*p == 0) goto endcmd; /*skip quote and any trailing blanks*/ while (*++p == ' ') ; if (*p != '\n' && *p != 0) goto endcmd; std::string copy = std::string(pstart, pend); switch(cmdind) { case cmdInclude: inputNewIncludeFile(inputPvt, copy.c_str()); break; case cmdSubstitute: addMacroReplacements(macPvt, copy.c_str()); break; default: fprintf(stderr, "msi: Logic error in makeSubstitutions\n"); inputErrPrint(inputPvt); abortExit(1); } expand = 0; } endcmd: if (expand && !opt_D) { STEP("Expanding to output stream"); n = macExpandString(macPvt, input, buffer, MAX_BUFFER_SIZE - 1); fputs(buffer, stdout); if (opt_V == 1 && n < 0) { fprintf(stderr, "msi: Error - undefined macros present\n"); opt_V++; } } } EXIT; } typedef struct inputFile { std::string filename; FILE *fp; int lineNum; } inputFile; struct inputData { std::list inputFileList; std::list pathList; char inputBuffer[MAX_BUFFER_SIZE]; inputData() { memset(inputBuffer, 0, sizeof(inputBuffer) * sizeof(inputBuffer[0])); }; }; static void inputOpenFile(inputData *pinputData, const char * const filename); static void inputCloseFile(inputData *pinputData); static void inputCloseAllFiles(inputData *pinputData); static void inputConstruct(inputData **ppvt) { *ppvt = new inputData; } static void inputDestruct(inputData * const pinputData) { inputCloseAllFiles(pinputData); delete(pinputData); } static void inputAddPath(inputData * const pinputData, const char * const path) { const char *pcolon; const char *pdir; size_t len; const char sep = *OSI_PATH_LIST_SEPARATOR; ENTER; pdir = path; /*an empty name at beginning, middle, or end means current directory*/ while (pdir && *pdir) { bool emptyName = (*pdir == sep); if (emptyName) ++pdir; std::string directory; if (!emptyName) { pcolon = strchr(pdir, sep); len = (pcolon ? (pcolon - pdir) : strlen(pdir)); if (len > 0) { directory = std::string(pdir, len); pdir = pcolon; /*unless at end skip past first colon*/ if (pdir && *(pdir + 1) != 0) ++pdir; } else { /*must have been trailing : */ emptyName = true; } } if (emptyName) { directory = "."; } pinputData->pathList.push_back(directory); } EXIT; } static void inputBegin(inputData * const pinputData, const char * const fileName) { ENTER; inputCloseAllFiles(pinputData); inputOpenFile(pinputData, fileName); EXIT; } static char *inputNextLine(inputData * const pinputData) { std::list& inFileList = pinputData->inputFileList; ENTER; while (!inFileList.empty()) { inputFile& inFile = inFileList.front(); char *pline = fgets(pinputData->inputBuffer, MAX_BUFFER_SIZE, inFile.fp); if (pline) { ++inFile.lineNum; EXITS(pline); return pline; } inputCloseFile(pinputData); } EXITD(0); return 0; } static void inputNewIncludeFile(inputData * const pinputData, const char * const name) { ENTER; inputOpenFile(pinputData,name); EXIT; } static void inputErrPrint(const inputData *const pinputData) { ENTER; fprintf(stderr, "input: '%s' at ", pinputData->inputBuffer); const std::list& inFileList = pinputData->inputFileList; std::list::const_iterator inFileIt = inFileList.begin(); while (inFileIt != inFileList.end()) { fprintf(stderr, "line %d of ", inFileIt->lineNum); if (!inFileIt->filename.empty()) { fprintf(stderr, " file %s\n", inFileIt->filename.c_str()); } else { fprintf(stderr, "stdin:\n"); } if (++inFileIt != inFileList.end()) { fprintf(stderr, " included from "); } else { fprintf(stderr,"\n"); } } fprintf(stderr,"\n"); EXIT; } static void inputOpenFile(inputData *pinputData, const char * const filename) { std::list& pathList = pinputData->pathList; std::list::iterator pathIt = pathList.end(); std::string fullname; FILE *fp = 0; ENTER; if (!filename) { STEP("Using stdin"); fp = stdin; } else if (pathList.empty() || strchr(filename, '/')){ STEPS("Opening ", filename); fp = fopen(filename, "r"); } else { pathIt = pathList.begin(); while(pathIt != pathList.end()) { fullname = *pathIt + "/" + filename; STEPS("Trying", filename); fp = fopen(fullname.c_str(), "r"); if (fp) break; ++pathIt; } } if (!fp) { fprintf(stderr, "msi: Can't open file '%s'\n", filename); inputErrPrint(pinputData); abortExit(1); } STEP("File opened"); inputFile inFile = inputFile(); if (pathIt != pathList.end()) { inFile.filename = fullname; } else if (filename) { inFile.filename = filename; } else { inFile.filename = "stdin"; } if (opt_D) { int hash = epicsStrHash(inFile.filename.c_str(), 12345); int i = 0; int match = 0; while (i < numDeps) { if (hash == depHashes[i++]) { match = 1; break; } } if (!match) { const char *wrap = numDeps ? " \\\n" : ""; printf("%s %s", wrap, inFile.filename.c_str()); if (numDeps < MAX_DEPS) { depHashes[numDeps++] = hash; } else { fprintf(stderr, "msi: More than %d dependencies!\n", MAX_DEPS); depHashes[0] = hash; } } } inFile.fp = fp; pinputData->inputFileList.push_front(inFile); EXIT; } static void inputCloseFile(inputData *pinputData) { std::list& inFileList = pinputData->inputFileList; ENTER; if(!inFileList.empty()) { inputFile& inFile = inFileList.front(); if (fclose(inFile.fp)) fprintf(stderr, "msi: Can't close input file '%s'\n", inFile.filename.c_str()); inFileList.erase(inFileList.begin()); } EXIT; } static void inputCloseAllFiles(inputData *pinputData) { ENTER; const std::list& inFileList = pinputData->inputFileList; while(!inFileList.empty()) { inputCloseFile(pinputData); } EXIT; } /*start of code that handles substitution file*/ typedef enum { tokenLBrace, tokenRBrace, tokenSeparator, tokenString, tokenEOF } tokenType; typedef struct subFile { std::string substitutionName; FILE *fp; int lineNum; char inputBuffer[MAX_BUFFER_SIZE]; char *pnextChar; tokenType token; char string[MAX_BUFFER_SIZE]; } subFile; struct subInfo { subFile *psubFile; bool isFile; char *filename; bool isPattern; std::list patternList; std::string macroReplacements; subInfo() : psubFile(NULL), isFile(false), filename(NULL), isPattern(false) {}; }; static char *subGetNextLine(subFile *psubFile); static tokenType subGetNextToken(subFile *psubFile); static void subFileErrPrint(subFile *psubFile, const char * message); static void freeSubFile(subInfo *psubInfo); static void freePattern(subInfo *psubInfo); static void catMacroReplacements(subInfo *psubInfo,const char *value); void freeSubFile(subInfo *psubInfo) { subFile *psubFile = psubInfo->psubFile; ENTER; if (psubFile->fp) { if (fclose(psubFile->fp)) fprintf(stderr, "msi: Can't close substitution file\n"); } delete(psubFile); free(psubInfo->filename); psubInfo->psubFile = 0; EXIT; } void freePattern(subInfo *psubInfo) { ENTER; psubInfo->patternList.clear(); psubInfo->isPattern = false; EXIT; } static void substituteDestruct(subInfo * const psubInfo) { ENTER; freeSubFile(psubInfo); freePattern(psubInfo); delete(psubInfo); EXIT; } static void substituteOpen(subInfo **ppvt, const std::string& substitutionName) { subInfo *psubInfo; subFile *psubFile; FILE *fp; ENTER; psubInfo = new subInfo; *ppvt = psubInfo; psubFile = new subFile; psubInfo->psubFile = psubFile; fp = fopen(substitutionName.c_str(), "r"); if (!fp) { fprintf(stderr, "msi: Can't open file '%s'\n", substitutionName.c_str()); abortExit(1); } psubFile->substitutionName = substitutionName; psubFile->fp = fp; psubFile->lineNum = 1; psubFile->inputBuffer[0] = 0; psubFile->pnextChar = &psubFile->inputBuffer[0]; subGetNextToken(psubFile); EXIT; } static bool substituteGetGlobalSet(subInfo * const psubInfo) { subFile *psubFile = psubInfo->psubFile; ENTER; while (psubFile->token == tokenSeparator) subGetNextToken(psubFile); if (psubFile->token == tokenString && strcmp(psubFile->string, "global") == 0) { subGetNextToken(psubFile); EXITD(1); return true; } EXITD(0); return false; } static bool substituteGetNextSet(subInfo * const psubInfo,char **filename) { subFile *psubFile = psubInfo->psubFile; ENTER; *filename = 0; while (psubFile->token == tokenSeparator) subGetNextToken(psubFile); if (psubFile->token == tokenEOF) { EXITD(0); return false; } if (psubFile->token == tokenString && strcmp(psubFile->string, "file") == 0) { size_t len; STEP("Parsed 'file'"); psubInfo->isFile = true; if (subGetNextToken(psubFile) != tokenString) { subFileErrPrint(psubFile, "Parse error, expecting a filename"); abortExit(1); } freePattern(psubInfo); free(psubInfo->filename); len = strlen(psubFile->string); if (psubFile->string[0] == '"' && psubFile->string[len - 1] == '"') { psubFile->string[len - 1] = '\0'; psubInfo->filename = macEnvExpand(psubFile->string + 1); } else psubInfo->filename = macEnvExpand(psubFile->string); STEPS("Parsed filename", psubInfo->filename); while (subGetNextToken(psubFile) == tokenSeparator); if (psubFile->token != tokenLBrace) { subFileErrPrint(psubFile, "Parse error, expecting '{'"); abortExit(1); } STEP("Parsed '{'"); subGetNextToken(psubFile); } *filename = psubInfo->filename; while (psubFile->token == tokenSeparator) subGetNextToken(psubFile); if (psubFile->token == tokenLBrace) { EXITD(1); return true; } if (psubFile->token == tokenRBrace) { subFileErrPrint(psubFile, "Parse error, unexpected '}'"); abortExit(1); } if (psubFile->token != tokenString || strcmp(psubFile->string, "pattern") != 0) { subFileErrPrint(psubFile, "Parse error, expecting 'pattern'"); abortExit(1); } STEP("Parsed 'pattern'"); freePattern(psubInfo); psubInfo->isPattern = true; while (subGetNextToken(psubFile) == tokenSeparator); if (psubFile->token != tokenLBrace) { subFileErrPrint(psubFile, "Parse error, expecting '{'"); abortExit(1); } STEP("Parsed '{'"); while (true) { while (subGetNextToken(psubFile) == tokenSeparator); if (psubFile->token != tokenString) break; psubInfo->patternList.push_back(psubFile->string); } if (psubFile->token != tokenRBrace) { subFileErrPrint(psubFile, "Parse error, expecting '}'"); abortExit(1); } subGetNextToken(psubFile); EXITD(1); return true; } static const char *substituteGetGlobalReplacements(subInfo * const psubInfo) { subFile *psubFile = psubInfo->psubFile; ENTER; psubInfo->macroReplacements.clear(); while (psubFile->token == tokenSeparator) subGetNextToken(psubFile); if (psubFile->token == tokenRBrace && psubInfo->isFile) { psubInfo->isFile = false; free(psubInfo->filename); psubInfo->filename = 0; freePattern(psubInfo); subGetNextToken(psubFile); EXITD(0); return 0; } if (psubFile->token == tokenEOF) { EXITD(0); return 0; } if (psubFile->token != tokenLBrace) { EXITD(0); return 0; } while (true) { switch(subGetNextToken(psubFile)) { case tokenRBrace: subGetNextToken(psubFile); EXITS(psubInfo->macroReplacements.c_str()); return psubInfo->macroReplacements.c_str(); case tokenSeparator: catMacroReplacements(psubInfo, ","); break; case tokenString: catMacroReplacements(psubInfo, psubFile->string); break; case tokenLBrace: subFileErrPrint(psubFile, "Parse error, unexpected '{'"); abortExit(1); case tokenEOF: subFileErrPrint(psubFile, "Parse error, incomplete file?"); abortExit(1); } } } static const char *substituteGetReplacements(subInfo * const psubInfo) { subFile *psubFile = psubInfo->psubFile; ENTER; psubInfo->macroReplacements.clear(); while (psubFile->token == tokenSeparator) subGetNextToken(psubFile); if (psubFile->token==tokenRBrace && psubInfo->isFile) { psubInfo->isFile = false; free(psubInfo->filename); psubInfo->filename = 0; freePattern(psubInfo); subGetNextToken(psubFile); EXITD(0); return 0; } if (psubFile->token == tokenEOF) { EXITD(0); return 0; } if (psubFile->token != tokenLBrace) { EXITD(0); return 0; } if (psubInfo->isPattern) { bool gotFirstPattern = false; while (subGetNextToken(psubFile) == tokenSeparator); std::list& patternList = psubInfo->patternList; std::list::iterator patternIt = patternList.begin(); while (true) { if (psubFile->token == tokenRBrace) { subGetNextToken(psubFile); EXITS(psubInfo->macroReplacements.c_str()); return psubInfo->macroReplacements.c_str(); } if (psubFile->token != tokenString) { subFileErrPrint(psubFile,"Parse error, expecting macro value"); abortExit(1); } if (gotFirstPattern) catMacroReplacements(psubInfo, ","); gotFirstPattern = true; if (patternIt != patternList.end()) { catMacroReplacements(psubInfo, patternIt->c_str()); catMacroReplacements(psubInfo, "="); catMacroReplacements(psubInfo, psubFile->string); ++patternIt; } else { subFileErrPrint(psubFile, "Warning, too many values given"); } while (subGetNextToken(psubFile) == tokenSeparator); } } else while(true) { switch(subGetNextToken(psubFile)) { case tokenRBrace: subGetNextToken(psubFile); EXITS(psubInfo->macroReplacements.c_str()); return psubInfo->macroReplacements.c_str(); case tokenSeparator: catMacroReplacements(psubInfo, ","); break; case tokenString: catMacroReplacements(psubInfo, psubFile->string); break; case tokenLBrace: subFileErrPrint(psubFile, "Parse error, unexpected '{'"); abortExit(1); case tokenEOF: subFileErrPrint(psubFile, "Parse error, incomplete file?"); abortExit(1); } } } static char *subGetNextLine(subFile *psubFile) { char *pline; ENTER; do { pline = fgets(psubFile->inputBuffer, MAX_BUFFER_SIZE, psubFile->fp); ++psubFile->lineNum; } while (pline && psubFile->inputBuffer[0] == '#'); if (!pline) { psubFile->token = tokenEOF; psubFile->inputBuffer[0] = 0; psubFile->pnextChar = 0; EXITD(0); return 0; } psubFile->pnextChar = &psubFile->inputBuffer[0]; EXITS(&psubFile->inputBuffer[0]); return &psubFile->inputBuffer[0]; } static void subFileErrPrint(subFile *psubFile, const char * message) { fprintf(stderr, "msi: %s\n",message); fprintf(stderr, " in substitution file '%s' at line %d:\n %s", psubFile->substitutionName.c_str(), psubFile->lineNum, psubFile->inputBuffer); } static tokenType subGetNextToken(subFile *psubFile) { char *p, *pto; ENTER; p = psubFile->pnextChar; if (!p) { STEP("Got EOF"); psubFile->token = tokenEOF; goto done; } if (*p == 0 || *p == '\n' || *p == '#') { STEP("Got newline/comment"); p = subGetNextLine(psubFile); psubFile->token = p ? tokenSeparator : tokenEOF; goto done; } while (isspace((int) *p)) p++; if (*p == '{') { STEP("Got '{'"); psubFile->token = tokenLBrace; psubFile->pnextChar = ++p; goto done; } if (*p == '}') { STEP("Got '}'"); psubFile->token = tokenRBrace; psubFile->pnextChar = ++p; goto done; } if (*p == 0 || isspace((int) *p) || *p == ',') { STEP("Got space/comma"); while (isspace((int) *p) || *p == ',') p++; psubFile->token = tokenSeparator; psubFile->pnextChar = p; goto done; } /*now handle quoted strings*/ if (*p == '"') { STEP("Got '\"'"); pto = &psubFile->string[0]; *pto++ = *p++; while (*p != '"') { if (*p == 0 || *p == '\n') { subFileErrPrint(psubFile, "Strings must be on single line\n"); abortExit(1); } /*allow escape for embeded quote*/ if ((p[0] == '\\') && p[1] == '"') { *pto++ = *p++; *pto++ = *p++; continue; } *pto++ = *p++; } *pto++ = *p++; psubFile->pnextChar = p; *pto = 0; psubFile->token = tokenString; goto done; } /*Now take anything up to next non String token and not space*/ pto = &psubFile->string[0]; while (!isspace((int) *p) && (strspn(p, "\",{}") == 0)) *pto++ = *p++; *pto = 0; STEPS("Got bareword", psubFile->string); psubFile->pnextChar = p; psubFile->token = tokenString; done: EXITD(psubFile->token); return psubFile->token; } static void catMacroReplacements(subInfo *psubInfo, const char *value) { ENTER; STEPS("Appending", value); psubInfo->macroReplacements += value; EXIT; }