/*** UTIL.C -- Data structure manipulation functions *************************** * * Copyright (c) 1988-1990, Microsoft Corporation. All rights reserved. * * Purpose: * This module contains routines manipulating the Data Structures of NMAKE. The * routines are independent of the Mode of execution (Real/Bound). * * Revision History: * 01-Feb-1994 HV Turn off extra info display. * 17-Jan-1994 HV Fixed bug #3548: possible bug in findMacroValues because we * are scanning 'string' byte-by-byte instead of char-by-char * 15-Nov-1993 JdR Major speed improvements * 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*() * 03-Jun-1993 HV Add helper local function TruncateString for findFirst. * 10-May-1993 HV Add include file mbstring.h * Change the str* functions to STR* * 08-Apr-1993 HV Rewrite prependPath() to use _splitpath() and _makepath() * 31-Mar-1993 HV Rewrite drive(), path(), filename(), and extension() to use * _splitpath() instead of parsing the pathname by itself. * 08-Jun-1992 SS Port to DOSX32 * 27-May-1992 RG Changed open_file to use _fsopen with _SH_DENYWR * 29-May-1990 SB ^$ was not becoming same as $$ for command lines ... * 01-May-1990 SB Nasty Preprocessor quirk bug in modifySpecialvalue() fixed * 27-Feb-1990 SB GP fault for '!if "$(debug"=="y"' fixed (bug t119) * 08-Feb-1990 SB GP fault for 'echo $(FOO:" =" ^) fixed * 06-Feb-1990 SB Handle $* etc in presence of Quoted names. * 02-Feb-1990 SB Add file_open() function * 31-Jan-1990 SB Debug changes; testAddr used to track problems at that addr * 08-Dec-1989 SB Changed SPRINTF() to avoid C6 warnings with -Oes * 04-Dec-1989 SB ZFormat() proto was misspelled as Zformat() * 01-Dec-1989 SB realloc_memory() added; allocate() uses _msize() now * 22-Nov-1989 SB Add strcmpiquote() and unQuote() * 22-Nov-1989 SB add #ifdef DEBUG_MEMORY funcs free_memory() and mem_status() * 13-Nov-1989 SB restoreEnv() function unreferenced * 08-Oct-1989 SB Added searchBucket(); find() now checks equivalence of quoted * and unquoted versions of a target. * 06-Sep-1989 SB $* in in-line files was clobbering Global variable 'buf' * 18-Aug-1989 SB heapdump() gets two parameters * 03-Jul-1989 SB moved curTime() to utilb.c and utilr.c to handle DOSONLY ver * 30-Jun-1989 SB added curTime() to get current Time. * 28-Jun-1989 SB changed copyEnviron() * 05-Jun-1989 SB makeString("") instead of using "" in DGROUP for null macro * 21-May-1989 SB modified find() to understand that targets 'foo.c', '.\foo.c' * and './foo.c' are the same. * 13-May-1989 SB Added functions path(), drive(), filename(), extension(), * strbskip(), strbscan() instead of ZTOOLS library * 12-May-1989 SB Fixed bug in substitute strings * 10-May-1989 SB Added stuff for ESCH handling changes in Quotes; * 01-May-1989 SB changed return value of allocate(). * 14-Apr-1989 SB restoreEnv() created for macroBackInheritance * 05-Apr-1989 SB made all funcs NEAR; Reqd to make all function calls NEAR * 17-Mar-1989 SB substituteStrings() now has 3 new error checks & avoids GPs * 13-Mar-1989 SB ZFormat() was missing the legal case of '%%' * 22-Feb-1989 SB ZFormat() has buffer overflow check with extra parameter * and SPRINTF() gives a new error * 15-Feb-1989 SB Changed modifySpecialValue(), was not performing correctly * for $(@D) and $(@B) for some cases. * 13-Feb-1989 SB Made Prototypes for ZTools Library functions as extern * 5-Dec-1988 SB Made SPRINTF() cdecl as NMAKE now uses Pascal calling * 25-Nov-1988 SB Added SPRINTF() and ZFormat() and also added prototypes for * functions used from ZTools Library (6 of them) * 10-Nov-1988 SB Removed mixed mode functions (bound & real) to utilr.c * & utilb.c; corr globals/debug data also moved * 10-Oct-1988 SB Add Comments for hash(). * 18-Sep-1988 RB Fix bad flag checking for targets in find(). * 15-Sep-1988 RB Move some def's out to GLOBALS. * 22-Aug-1988 RB Don't find undefined macros. * 17-Aug-1988 RB Clean up. Clear memory in allocate(). * 8-Jul-1988 rj Minor speedup (?) to find(). * 7-Jul-1988 rj Modified find and hash; less efficient, but case-indep. * 1-Jul-1988 rj Fixed line truncation after null special macro. * 30-Jun-1988 rj Fixed bug with checkDynamicDependency not handling $$. * 29-Jun-1988 rj Fixed bug with extra * after expanding $**. * Fixed abysmal screwup with $$(@?). * Fixed handling of F, B, R modifiers. * 22-Jun-1988 rj Added friendly filename truncation (findFirst). * 22-Jun-1988 rj Fixed bug #3 (.abc.def.ghi not detected). * 16-Jun-1988 rj Modified several routines to look for escape * character when walking through strings. * 27-May-1988 rb Fixed space-appending on list-expansion macros. * Don't include trailing path separator in $(@D). * *******************************************************************************/ #include "nmake.h" #include "nmmsg.h" #include "proto.h" #include "globals.h" #include "grammar.h" /* Prototypes of functions local to this module */ LOCAL void NEAR putValue(char**, char**, char**, char**, char*, unsigned*, char *); LOCAL void NEAR substituteStrings(char**, char**, char**, char**, char*, unsigned*, char *); LOCAL char * NEAR isolateMacroName(char*, char*); LOCAL char * NEAR checkDynamicDependency(char*); LOCAL void NEAR increaseBuffer(char**, char**, char**, unsigned*, char *); LOCAL void NEAR putSpecial(char**, char**, char**, char**, unsigned*, unsigned, char *); char * NEAR modifySpecialValue(char, char*, char*); LOCAL BOOL NEAR ZFormat(char *, unsigned, char *, char *); LOCAL STRINGLIST * NEAR searchBucket(char *, STRINGLIST **, unsigned); LOCAL int NEAR envVars(char **environ); /* Prototypes of functions used by ZFormat from ZTools Library */ char * NEAR strbscan(char *, char *); LOCAL char * NEAR strbskip(char *, char *); LOCAL int NEAR drive(const char *, char *); LOCAL int NEAR path(const char *, char *); LOCAL int NEAR filename(const char *, char *); LOCAL int NEAR extension(const char *, char *); #ifdef HEAP extern void NEAR heapdump(char *file, int line); #endif #ifdef DEBUG_MEMORY unsigned long blocksize = 500L; BOOL fDebugMem = FALSE; FILE *memory; unsigned long freecount = 0L; unsigned testAddr = 0xdf78; #endif char special1[] = "*@= blocksize) { mem_status(); blocksize += 500L ; } if (_heapchk() != _HEAPOK) fprintf(stderr, "Error: heap is not Ok"); } #endif #ifdef HEAP // if (_heapchk() != _HEAPOK) // fprintf(stderr, "Error: heap is not Ok"); size = _msize(chunk); if (fHeapChk) { chunk[0] = 'N'; chunk[1] = 'M'; chunk[size - 2] = 'K'; chunk[size - 1] = 'E'; chunk += 2; } #endif #ifdef DEBUG_MEMORY if (FP_OFF(chunk) == testAddr) fprintf(stderr, "Changed ...allocated\n"); #endif return((void*)chunk); } void * NEAR alloc_stringlist(void) /* Number of bytes requested */ { STRINGLIST *chunk; #if defined(STATISTICS) CntAllocStrList++; #endif if (PtrFreeStrList != NULL) { chunk = PtrFreeStrList; PtrFreeStrList = chunk->next; } else { if (AllocFreeCnt < sizeof(STRINGLIST)) { PtrAlloc = (char *)malloc(ALLOCBLKSIZE); if (PtrAlloc == NULL) { #ifdef DEBUG_MEMORY if (fDebug) fprintf(memory, "out of memory after allocating %lu bytes\n", TotalAlloc); #endif makeError(currentLine, OUT_OF_MEMORY); } #if !defined(NDEBUG) && !defined(NT_BUILD) TotalAlloc += ALLOCBLKSIZE; CntAlloc++; #endif AllocFreeCnt = ALLOCBLKSIZE; } chunk = (STRINGLIST *)PtrAlloc; PtrAlloc += sizeof(STRINGLIST); AllocFreeCnt -= sizeof(STRINGLIST); } chunk->next = NULL; chunk->text = NULL; return (void *)chunk; } void NEAR free_stringlist(STRINGLIST *pMem) { #if defined(STATISTICS) CntFreeStrList++; #endif if (PtrFreeStrList == NULL) { pMem->next = NULL; PtrFreeStrList = pMem; } else { #ifdef HEAP STRINGLIST *tmp = PtrFreeStrList; do { if (tmp == pMem) { fprintf(stderr, "free same pointer twice: %p\n", pMem); return; } tmp = tmp->next; } while (tmp); pMem->text = NULL; #endif pMem->next = PtrFreeStrList; PtrFreeStrList = pMem; } } #ifdef HEAP void NEAR free_memory(pMem) void *pMem; { char *p = pMem; unsigned uFree; #ifdef DEBUG_MEMORY if (FP_OFF(pMem) == testAddr) fprintf(stderr, "Changed ... freed\n"); #endif if (fHeapChk) { p -= 2; if (p[0] != 'N' && p[1] != 'M') fprintf(stderr, "Alloc start signature error at %p\n", pMem); #if defined(FLAT) __try { uFree = _msize(p); } __except(1) { fprintf(stderr, "non malloc ptr freed at %p\n", pMem); return; } #else // if (_heapchk() != _HEAPOK) // fprintf(stderr, "Error: heap is not Ok"); uFree = _msize(p); #endif if (p[uFree - 2] != 'K' && p[uFree - 1] != 'E') fprintf(stderr, "Alloc end signature error at %p\n", pMem); #ifdef FLAT memset(p, 0xFF, uFree); #else _fmemset(p, 0xFF, uFree); #endif } #ifdef DEBUG_MEMORY freecount += uFree; #endif free(p); } void * NEAR realloc_memory(pMem, uSize) void *pMem; unsigned uSize; { unsigned old; unsigned new; void *pNew; char *p = pMem; #ifdef DEBUG_MEMORY if (FP_OFF(pMem) == testAddr) fprintf(stderr, "Changed ... reallocated\n"); #endif if (fHeapChk) { p -= 2; if (p[0] != 'N' && p[1] != 'M') fprintf(stderr, "Alloc start signature error at %p\n", pMem); old = _msize(p); if (p[old - 2] != 'K' && p[old - 1] != 'E') fprintf(stderr, "Alloc end signature error at %p\n", pMem); uSize += 4; } pNew = realloc(p, uSize); if (fHeapChk) { if (pNew == NULL) return(pNew); new = _msize(pNew); } #ifdef DEBUG_MEMORY if (new > old) TotalAlloc += new - old; else freecount += old - new; #endif if (fHeapChk) { if (p != pNew) #ifdef FLAT memset(p, 0xFF, old); #else _fmemset(p, 0xFF, old); #endif } p = (char *)pNew; if (fHeapChk) { p[new - 2] = 'K'; p[new - 1] = 'E'; p += 2; } return(p); } #endif #if 0 void NEAR free_memory(void *pMem) { #if defined(FLAT) __try { (void)_msize(pMem); } __except(1) { fprintf(stderr, "non malloc ptr freed at %p\n", pMem); return; } #endif free(pMem); } #endif #ifdef DEBUG_MEMORY void NEAR mem_status(void) { if (fDebug) { fprintf(memory, "allocated : %lu ", TotalAlloc); fprintf(memory, "Freed : %lu ", freecount); fprintf(memory, "Still allocated : %lu\n", TotalAlloc - freecount); fprintf(memory, "Mem Avail in Near Heap : %u\n", _memavl()); } } #endif char * NEAR makeString(s) /* allocates space, copies*/ char *s; /* the given string into */ { /* the newly allocated */ char *t; /* space, and returns ptr*/ unsigned l = _ftcslen(s) + 1; t = (char *)rallocate(l); memcpy(t, s, l); return(t); } void NEAR prependItem(list, element) /* makes element the head */ STRINGLIST **list; /* of list */ STRINGLIST *element; { element->next = *list; *list = element; } void NEAR appendItem(list, element) /* makes element the tail */ STRINGLIST **list; /* of list */ STRINGLIST *element; { for (; *list; list = &(*list)->next); *list = element; } /* hash - returns hash value corresponding to a string * * Purpose: * This is a hash function. The hash function uses the following Algorithm -- * Add the characters making up the string (s) to get N (ASCII values) * N mod total ,gives the hash value, * where, total is MAXMACRO for macros * MAXTARGET targets * Additionally, for targets it takes Uppercase Values alone, since, targets * are generally filenames and DOS/OS2 filenames are case independent. * * Input: * s = name for which a hash is required * total = Constant used in the hash function * targetFlag = boolean flag; true for targets, false for macros * * Output: * Returns hash value between 0 and (total-1) * */ unsigned NEAR hash(s, total, targetFlag) char *s; unsigned total; BOOL targetFlag; { unsigned n; unsigned c; if (targetFlag) { for (n = 0; c = *s; (n += c), s++) if (c == '/') c = '\\'; /* slash == backslash in targets */ else c = _totupper(c); /* lower-case == upper-case in targets */ } else for (n = 0; *s; n += *s++); return(n % total); } /* * find - look up a string in a hash table * * Look up a macro or target name in a hash table and return the entry * or NULL. * If a macro and undefined, return NULL. * Targets get matched in a special way because of filename equivalence. */ STRINGLIST * NEAR find(str, limit, table, targetFlag) char *str; unsigned limit; STRINGLIST *table[]; BOOL targetFlag; { unsigned n; char *string = str; char *quote; STRINGLIST *found; BOOL fAllocStr = FALSE; if (*string) { n = hash(string, limit, targetFlag); if (targetFlag) { #if defined(STATISTICS) CntfindTarget++; #endif found = searchBucket(string, table, n); if (found) return(found); //Look for .\string if (!_ftcsncmp(string, ".\\", 2) || !_ftcsncmp(string, "./", 2)) string += 2; else { string = (char *)rallocate(2 + _ftcslen(str) + 1); _ftcscat(_ftcscpy(string, ".\\"), str); fAllocStr = (BOOL)TRUE; } n = hash(string, limit, targetFlag); found = searchBucket(string, table, n); if (found) { if (fAllocStr) FREE(string); return(found); } //Look for ./string if (string != (str + 2)) string[1] = '/'; n = hash(string, limit, targetFlag); found = searchBucket(string, table, n); if (fAllocStr) FREE(string); if (found) return(found); //Look for "foo" or foo if (*str == '"') { quote = unQuote(str); } else { unsigned len = _ftcslen(str) + 2; quote = allocate(len + 1); _ftcscat(_ftcscat(_ftcscpy(quote, "\""), str), "\""); } n = hash(quote, limit, targetFlag); found = searchBucket(quote, table, n); FREE(quote); return found; } else { for (found = table[n]; found; found = found->next) if (!_ftcscmp(found->text, string)) return((((MACRODEF *)found)->flags & M_UNDEFINED) ? NULL : found); } } return(NULL); } /* * FINDMACROVALUES -- * looks up a macro's value in hash table, prepends to list a STRINGLIST * element holding pointer to macro's text, then recurses on any macro * invocations in the value * * The lexer checks for overrun in names (they must be < 128 chars). * If a longer, undefined macro is only referred to in the value of * another macro which is never invoked, the error will not be flagged. * I think this is reasonable behavior. * * MACRO NAMES CAN ONLY CONSIST OF ALPHANUMERIC CHARS AND UNDERSCORE * * we pass a null list pointer-pointer if we just want to check for cyclical * definitions w/o building the list. * * the name parameter is what's on the left side of an = when we're just * checking cyclical definitions. When we "find" the macros in a target * block, we have to pass the name of the macro whose text we're recursing * on in our recursive call to findMacroValues(). * * Might want to check into how to do this w/o recursion (is it possible?) * * This function is RECURSIVE. */ /* Added a fix to make this function handle expand macros which refer * to other recursive macros. * * levelSeen is the recLevel at which a macroname was first seen so that * the appropriate expansion can be calculated (even when recursing ...) */ #pragma check_stack(on) BOOL NEAR findMacroValues(char *string, /* string to check*/ STRINGLIST **list, /* list to build */ STRINGLIST **newtail, /* tail of list to update */ char *name, /* name = string */ unsigned recLevel, /* recursion level*/ unsigned levelSeen, UCHAR flags) { char macroName[MAXNAME]; char *s; MACRODEF *p; STRINGLIST *q, *r, dummy, *tail; unsigned i; BOOL inQuotes = (BOOL) FALSE; /* flag when inside quote marks */ if (list) { if (newtail) { tail = *newtail; } else { tail = *list; if (tail) { while (tail->next) { tail = tail->next; } } } } else { tail = NULL; } for (s = string; *s; ++s) { /* walk the string*/ for (; *s && *s != '$'; s = _ftcsinc(s)) { /* find next macro*/ if (*s == '\"') inQuotes = (BOOL) !inQuotes; if (!inQuotes && *s == ESCH) { ++s; /* skip past ESCH */ if (*s == '\"') inQuotes = (BOOL) !inQuotes; } } if (!*s) break; /* move past '$' */ if (!s[1]) if (ON(flags, M_ENVIRONMENT_DEF)) { if (newtail) *newtail = tail; return(FALSE); } else makeError(currentLine, SYNTAX_ONE_DOLLAR); s = _ftcsinc(s); if (!inQuotes && *s == ESCH) { s = _ftcsinc(s); if (!MACRO_CHAR(*s)) if (ON(flags, M_ENVIRONMENT_DEF)) { if (newtail) *newtail = tail; return(FALSE); } else makeError(currentLine, SYNTAX_BAD_CHAR, *s); } if (*s == '$') { /* $$ = dynamic */ s = checkDynamicDependency(s); /* dependency */ continue; /* or just $$->$ */ } else if (*s == '(') { /* name is longer */ s = isolateMacroName(s+1, macroName); /* than 1 char */ if (_ftcschr(special1, *macroName)) continue; } else { if (_ftcschr(special1, *s)) continue; /* 1-letter macro */ if (!MACRO_CHAR(*s)) if (ON(flags, M_ENVIRONMENT_DEF)) { if (newtail) *newtail = tail; return(FALSE); } else makeError(currentLine, SYNTAX_ONE_DOLLAR); macroName[0] = *s; macroName[1] = '\0'; } // If list isn't NULL, allocate storage for a new node. Otherwise // this function was called purely to verify the macro name was // valid and we can just use the dummy node as a place holder. // // 2/28/92 BryanT dummy.text wasn't being initialized each // time. As a result, if we were to recurse // this function, whatever value was in text // on the last iteration is still there. // In the case where the macroName doesn't exist // in the the call to findMacro(), and the old // dummy->text field contained a '$', the // function would recurse infinitely. // Set to an empty string now // // q = (list) ? makeNewStrListElement() : &dummy; if (list != NULL) { q = makeNewStrListElement(); } else { dummy.next = NULL; dummy.text = makeString(" "); q = &dummy; } if (p = findMacro(macroName)) { // macro names are case sensitive if (name && !_ftcscmp(name, macroName)) { /* self-refer- */ r = p->values; /* ential macro */ for (i = recLevel; i != levelSeen && r; --i) r = r->next; /* (a = $a;b) */ q->text = (r) ? r->text : makeString(""); } else if (ON(p->flags, M_EXPANDING_THIS_ONE)) { /* recursive def */ if (ON(flags, M_ENVIRONMENT_DEF)) { if (newtail) *newtail = tail; return(FALSE); } else makeError(currentLine, CYCLE_IN_MACRODEF, macroName); } else q->text = p->values->text; } if (list) { /* if blding list */ if (!p || ON(p->flags, M_UNDEFINED)) q->text = makeString(""); /* if macro undefd*/ q->next = NULL; /* use NULL as its value */ if (tail) { tail->next = q; } else { *list = q; } tail = q; } /* if found text, */ if (!p || !_ftcschr(q->text, '$')) continue; /* and found $ in*/ SET(p->flags, M_EXPANDING_THIS_ONE); /* text, recurse */ findMacroValues(q->text, list, &tail, macroName, recLevel+1, (name && _ftcscmp(name, macroName)? recLevel : levelSeen), flags); CLEAR(p->flags, M_EXPANDING_THIS_ONE); } if (newtail) *newtail = tail; return(TRUE); } #pragma check_stack() /* * isolateMacroName -- returns pointer to name of macro in extended invocation * * arguments: s pointer to macro invocation * macro pointer to location to store macro's name * * returns: pointer to end of macro's name */ LOCAL char * NEAR isolateMacroName(s, macro) /* isolates name and moves s */ char *s; /* past closing paren */ char *macro; /* lexer already ckd for bad */ { /* syntax */ char *t; for (t = macro; *s && *s != ')' && *s != ':'; *t++ = *s++) { if (*s == ESCH) { s++; if (!MACRO_CHAR(*s)) makeError(currentLine, SYNTAX_BAD_CHAR, *s); } } while (*s != ')') { if (*s == ESCH) s++; if (!*s) break; s++; } if (*s != ')') makeError(currentLine, SYNTAX_NO_PAREN); *t = '\0'; if (t - macro > MAXNAME) makeError(currentLine, NAME_TOO_LONG); return(s); } /* figures out length of the special macro in question, and returns a ptr to * thee char after the last char in the invocation */ LOCAL char * NEAR checkDynamicDependency(s) char *s; { char *t; t = s + 1; if (*t == ESCH) return(t); /* If $^, leave us at the ^ */ if (*t == '(') if (*++t == ESCH) return(t); else if (*t == '@') { if (*++t == ESCH) makeError(currentLine, SYNTAX_BAD_CHAR, *++t); else if (*t == ')') return(t); else if (_ftcschr(special2, *t)) { if (*++t == ESCH) makeError(currentLine, SYNTAX_BAD_CHAR, *++t); else if (*t == ')') return(t); } } else { t = s + 1; /* invalid spec. mac. */ if (*t == ESCH) return(t); /* evals. to $( */ return(++t); } return(s); } /* char matched */ /* * removes and expands any macros that exist in the string macroStr. * could return a different string (in case expandMacros needs more * buffer size for macro expansion. it is the caller's responsibility * to free the string soon as it is not required.... */ char * NEAR removeMacros(macroStr) char *macroStr; { STRINGLIST *eMacros = NULL; STRINGLIST *m; if (_ftcschr(macroStr, '$')) { findMacroValues(macroStr, &eMacros, NULL, NULL, 0, 0, 0); #ifdef DEBUG_MACRO_EXPANSION #ifdef HEAP heapdump(__FILE__, __LINE__); #endif #endif m = eMacros; macroStr = expandMacros(macroStr, &eMacros); #ifdef DEBUG_MACRO_EXPANSION #ifdef HEAP heapdump(__FILE__, __LINE__); #endif #endif while (eMacros = m) { #if 0 /* free macros' text */ FREE(m->text); //NOTE: This is unsafe !!!! #endif m = m->next; FREE_STRINGLIST(eMacros); } } return(macroStr); } /* * expandMacros -- expand all macros in a string s * * arguments: s string to expand * macros list of macros being expanded (for recursive calls) * * actions: allocate room for expanded string * look for macros in string (handling ESCH properly (v1.5)) * parse macro--determine its type * use putSpecial to handle special macros * recurse on list of macros * use putValue to put value of just-found macro in string * return expanded string * * returns: string with all macros expanded * * CALLER CHECKS TO SEE IF _ftcschr(STRING, '$') IN ORER TO CALL THIS. * this doesn't work for HUGE macros yet. need to make data far. * * we save the original string and the list of ptrs to macro values * to be substituted. * the caller has to free the expansion buffer */ /* * expandMacros updates the macros pointer and frees the skipped elements */ char * NEAR expandMacros(s, macros) char *s; /* text to expand */ STRINGLIST **macros; { STRINGLIST *p; char *t, *end; char *text, *xresult; BOOL inQuotes = (BOOL) FALSE; /* flag when inside quote marks */ char *w; BOOL freeFlag = FALSE; char resultbuffer[MAXBUF]; unsigned len = MAXBUF; char *result = resultbuffer; end = result + MAXBUF; for (t = result; *s;) { /* look for macros*/ for (; *s && *s != '$'; *t++ = *s++) { /* as we copy the*/ if (t == end) { /* string */ increaseBuffer(&result, &t, &end, &len, &resultbuffer[0]); } if (*s == '\"') inQuotes = (BOOL) !inQuotes; if (!inQuotes && *s == ESCH) { *t++ = ESCH; s++; if (*s == '\"') inQuotes = (BOOL) !inQuotes; } } if (t == end) { /* string */ increaseBuffer(&result, &t, &end, &len, &resultbuffer[0]); } if (!*s) break; /* s exhausted */ w = (s+1); /* don't check for ^ here; already did in findMacroValues*/ if (*w == '(' /* found a macro */ && _ftcschr(special1, *(w+1))) { putSpecial(&result, &s, &t, &end, &len, X_SPECIAL_MACRO, &resultbuffer[0]); continue; } else if (*w++ == '$') { /* double ($$) */ if (*w == ESCH) /* $$^... */ putSpecial(&result, &s, &t, &end, &len, DOLLAR_MACRO, &resultbuffer[0]); else if (*w == '@') /* $$@ */ putSpecial(&result, &s, &t, &end, &len, DYNAMIC_MACRO, &resultbuffer[0]); else if ((*w == '(') && (*++w == '@') && (*w == ')')) putSpecial(&result, &s, &t, &end, &len, DYNAMIC_MACRO, &resultbuffer[0]); else if (((*++w=='F') || (*w=='D') || (*w=='B') || (*w=='R')) && (*++w == ')')) putSpecial(&result, &s, &t, &end, &len, X_DYNAMIC_MACRO, &resultbuffer[0]); else putSpecial(&result, &s, &t, &end, &len, DOLLAR_MACRO, &resultbuffer[0]); /* $$ */ continue; } else if (_ftcschr(special1, s[1])) { /* $?*< */ putSpecial(&result, &s, &t, &end, &len, SPECIAL_MACRO, &resultbuffer[0]); continue; } if (!*macros) makeError(currentLine, MACRO_INTERNAL); // // skip this element in the macros list // if (_ftcschr((*macros)->text, '$')) { /* recurse */ p = *macros; *macros = (*macros)->next; text = expandMacros(p->text, macros); freeFlag = TRUE; } else { text = (*macros)->text; *macros = (*macros)->next; } putValue(&result, &s, &t, &end, text, &len, &resultbuffer[0]); if (freeFlag) { FREE(text); freeFlag = FALSE; } } if (t == end) { increaseBuffer(&result, &t, &end, &len, &resultbuffer[0]); } *t++ = '\0'; //Allocate result buffer if (!(xresult = rallocate(t-result))) makeError(currentLine, MACRO_TOO_LONG); memcpy(xresult, result, t-result); return(xresult); } /* * increaseBuffer -- increase the size of a string buffer, with error check * * arguments: result pointer to pointer to start of buffer * t pointer to pointer to end of buffer (before expansion) * end pointer to pointer to end of buffer (after expansion) * len pointer to amount by which to expand buffer * first address of initial stack buffer * * actions: check for out of memory * allocate new buffer * reset pointers properly * * modifies: t, end to point to previous end and new end of buffer * * uses 0 as line number because by the time we hit an error in this routine, * the line number will be set at the last line of the makefile (because we'll * have already read and parsed the file) */ LOCAL void NEAR increaseBuffer(result, t, end, len, first) char **result; char **t; char **end; unsigned *len; char *first; { unsigned newSize; #ifndef FLAT if (*len == MAXSEGMENT) /* already at limit */ makeError(currentLine, MACRO_TOO_LONG); /* for memory usage */ #endif // // determine if result points to the firstbuffer and make a dynamic copy first. // if (*result == first) { char *p = rallocate(*len); memcpy(p, *result, *len); *result = p; } newSize = *len + MAXBUF; #ifdef DEBUG if (fDebug) { #ifdef HEAP heapdump(__FILE__, __LINE__); #endif fprintf(stderr,"\t\tAttempting to reallocate %d bytes to %d\n", *len, newSize); } #endif if (!(*result = REALLOC(*result, newSize))) makeError(currentLine, MACRO_TOO_LONG); *t = *result + *len; /* reset pointers, len*/ *len = newSize; *end = *result + *len; } /* * putSpecial -- expand value of special macro * * arguments: result ppointer to start of string being expanded * name ppointer to macro name being expanded * dest ppointer to place to store expanded value * end ppointer to end of dest's buffer * length pointer to amount by which to increase dest's buffer * which type of special macro * first address of initial stack buffer * * actions: depending on type of macro, set "value" equal to macro's value * if macro expands to a list, store whole list in "value" ($?, $*) * otherwise, modify value according to F, B, D, R flag * use putValue to insert the value in dest * * has to detect error if user tries $* etc. when they aren't defined * fix to handle string substitutions, whitespace around names, etc * right now list macros are limited to 1024 bytes total */ LOCAL void NEAR putSpecial(result, name, dest, end, length, which, first) char **result; char **name; char **dest; char **end; unsigned *length; unsigned which; /* find close paren */ char *first; { /* and move past it */ char *value = 0; STRINGLIST *p; BOOL listMacro = FALSE, modifier = FALSE, star = FALSE; unsigned i = 1; char c, nameBuf[MAXNAME], *temp; switch (which) { case X_SPECIAL_MACRO: i = 2; modifier = TRUE; case SPECIAL_MACRO: switch ((*name)[i]) { case '<': value = dollarLessThan; break; case '@': value = dollarAt; break; case '?': value = (char*) dollarQuestion; listMacro = TRUE; break; case '*': if ((*name)[i+1] != '*') { value = dollarStar; star = TRUE; break; } value = (char*) dollarStarStar; listMacro = TRUE; ++i; break; default: break; } ++i; break; case X_DYNAMIC_MACRO: i = 4; modifier = TRUE; case DYNAMIC_MACRO: value = dollarDollarAt; break; case DOLLAR_MACRO: if (*dest == *end) increaseBuffer(result, dest, end, length, first); *(*dest)++ = '$'; *name += 2; return; default: return; /* can't happen */ } if (!value) { for (temp = *name; *temp && *temp != ' ' && *temp != '\t'; temp++); c = *temp; *temp = '\0'; makeError(currentLine, ILLEGAL_SPECIAL_MACRO, *name); *temp = c; listMacro = FALSE; value = makeString(""); // value is freed below, must be on heap [rm] } if (listMacro) { char *pVal, *endVal; unsigned lenVal = MAXBUF; p = (STRINGLIST*) value; pVal = (char *)allocate(MAXBUF); endVal = pVal + MAXBUF; for (value = pVal; p; p = p->next) { temp = p->text; if (modifier) temp = modifySpecialValue((*name)[i], nameBuf, temp); while(*temp) { if (value == endVal) increaseBuffer(&pVal, &value, &endVal, &lenVal, NULL); *value++ = *temp++; } if (value == endVal) increaseBuffer(&pVal, &value, &endVal, &lenVal, NULL); *value = '\0'; /* * Append a space if there are more elements in the list. [RB] */ if (p->next) { *value++ = ' '; if (value == endVal) increaseBuffer(&pVal, &value, &endVal, &lenVal, NULL); *value = '\0'; } } value = pVal; } else { //For some reason 'buf' was being used here clobbering global 'buf // instead of nameBuf if (star) value = modifySpecialValue('R', nameBuf, value); if (modifier) value = modifySpecialValue((*name)[i], nameBuf, value); } putValue(result, name, dest, end, value, length, first); if (value != dollarAt && value != dollarDollarAt && value != dollarLessThan && (value < nameBuf || value >= nameBuf + MAXNAME)) FREE(value); } /*** modifySpecialValue -- alter path name according to modifier *************** * * Scope: * Local. * * Purpose: * The dynamic macros of NMAKE have modifiers F,B,D & R. This routine does the * job of producing a modified special value for a given filename. * * Input: * c -- determines the type of modification (modifier is one of F,B,D & R * buf -- location for storing modified value * value -- The path specification to be modified * * Output: * Returns a pointer to the modified value * * Errors/Warnings: * * Assumes: * That initially buf pointed to previously allocated memory of size MAXNAME. * * Modifies Globals: * * Uses Globals: * * Notes: * Given a path specification of the type "<.ext>", the * modifiers F,B,D and R stand for following -- * F - <.ext> - actual Filename * B - - Base filename * D - - Directory * R - - Real filename (filename without extension) * This routine handles OS/2 1.20 filenames as well. The last period in the * path specification is the start of the extension. When directory part is null * the function returns '.' for current directory. * * This function now handles quoted filenames too * *******************************************************************************/ char * NEAR modifySpecialValue(c, buf, value) char c; char *buf; char *value; { char *lastSlash, //last path separator from "\\/" *extension; //points to the extension BOOL fQuoted; lastSlash = extension = NULL; _ftcscpy(buf, value); fQuoted = (BOOL) (buf[0] == '"'); value = buf + _ftcslen(buf) - 1; //start from the end of pathname for (;value >= buf; value--) if (PATH_SEPARATOR(*value)) { //scan upto first path separator lastSlash = value; break; } else if (*value == '.' && !extension) //last '.' is extension extension = value; switch(c) { case 'D': if (lastSlash) { if (buf[1] == ':' && lastSlash == buf + 2) ++lastSlash; //'d:\foo.obj' --> 'd:\' *lastSlash = '\0'; } else if (buf[1] == ':') buf[2] = '\0'; //'d:foo.obj' --> 'd:' else _ftcscpy(buf, "."); //'foo.obj' --> '.' break; case 'B': if (extension) //for 'B' extension is clobbered *extension = '\0'; case 'F': if (lastSlash) buf = lastSlash + 1; else if (buf[1] == ':') //'d:foo.obj' --> foo for B buf+=2; //'d:foo.obj' --> foo.obj for F break; case 'R': if (extension) *extension = '\0'; //extension clobbered } if (fQuoted) { char *pEnd = _ftcschr(buf, '\0'); *pEnd++ = '"'; *pEnd = '\0'; } return(buf); } /* * putValue -- store expanded macro's value in dest and advance past it * * arguments: result ppointer to start of string being expanded * name ppointer to macro name being expanded * dest ppointer to place to store expanded value * end ppointer to end of dest's buffer * source pointer to text of expanded macro * length pointer to amount by which to increase dest's buffer * first address of initial stack buffer * * actions: if there is a substitution, call substituteStrings to do it * else copy source text into dest * advance *name past end of macro's invocation * * already did error checking in lexer */ LOCAL void NEAR putValue(result, name, dest, end, source, length, first) char **result; char **name; char **dest; char **end; char *source; unsigned *length; char *first; { char *s; char *t; /* temporary pointer */ if (*++*name == ESCH) ++*name; /* go past $ & ESCH if any*/ s = _ftcschr(*name, ':'); for (t = *name; *t && *t != ')'; t++) /* go find first non-escaped ) */ if (*t == ESCH) t++; if ((**name == '(') /* substitute only if there is */ && s /* a : before a non-escaped ) */ && (s < t)) { substituteStrings(result, &s, dest, end, source, length, first); *name = s; } else { for (; *source; *(*dest)++ = *source++) /* copy source into dest */ if (*dest == *end) increaseBuffer(result, dest, end, length, first); if (**name == '$') ++*name; /* go past $$ */ if (**name == '(') /* advance from ( to ) */ while (*++*name != ')'); else if (**name == '*' && *(*name + 1) == '*') ++*name; /* skip $** */ ++*name; /* move all the way past */ } } /* * substituteStrings -- perform macro substitution * * arguments: result ppointer to start of string being expanded * name ppointer to macro name being expanded * dest ppointer to place to store substituted value * end ppointer to end of dest's buffer * source pointer to text of expanded macro (before sub.) * length pointer to amount by which to increase dest's buffer * first address of initial stack buffer * * changes: [SB] * old, new now dynamically allocated; saves memory; 3 errors detected * for macro syntax in script files. * * note: [SB] * we could use lexer routines recursively if we get rid of the globals * and then these errors needn't be flagged. [?] * * actions: store text to convert from in old * store text to convert to in new * scan source text * when a match is found, copy new text into dest & * skip over old text * else copy one character from source text into dest * * returns: nothing */ LOCAL void NEAR substituteStrings(result, name, dest, end, source, length, first) char **result; char **name; char **dest; char **end; char *source; unsigned *length; char *first; { char *old, *new; char *pEq, *pPar, *t; char *s; unsigned i; ++*name; for (pEq = *name; *pEq && *pEq != '='; pEq++) if (*pEq == ESCH) pEq++; if (*pEq != '=') makeError(line, SYNTAX_NO_EQUALS); if (pEq == *name) makeError(line, SYNTAX_NO_SEQUENCE); for (pPar = pEq; *pPar && *pPar != ')'; pPar++) if (*pPar == ESCH) pPar++; if (*pPar != ')') makeError(line, SYNTAX_NO_PAREN); old = (char *)allocate((pEq - *name) + 1); for (s = old, t = *name; *t != '='; *s++ = *t++) if (*t == ESCH) ++t; *s = '\0'; i = _ftcslen(old); new = (char *)allocate(pPar - pEq); for (s = new, t++; *t != ')'; *s++ = *t++) if (*t == ESCH) ++t; *s = '\0'; *name = pPar + 1; while (*source) { if ((*source == *old) /* check for match*/ && !_ftcsncmp(source, old, i)) { /* copy new in for*/ for (s = new; *s; *(*dest)++ = *s++) /* old string */ if (*dest == *end) increaseBuffer(result, dest, end, length, first); source += i; continue; } if (*dest == *end) increaseBuffer(result, dest, end, length, first); *(*dest)++ = *source++; /* else copy 1 char */ } FREE(old); FREE(new); } /*** prependPath -- prepend the path from pszWildcard to pszFilename *********** * * Scope: * Global. * * Purpose: * This function is called to first extract the path (drive & dir parts) from * pszWildcard, the prepend that path to pszFilename. The result string is * a reconstruction of the full pathname. Normally, the pszWildcard parameter * is the same as the first parameter supplied to findFirst(), and pszFilename * is what returned by findFirst/findNext. * * Input: * pszWildcard -- Same as the first parameter supplied to findFirst() * pszFilename -- Same as the return value of findFirst/FindNext() * * Output: * Return the reconstructed full pathname. The user must be responsible to * free up the memory allocated by this string. * * Errors/Warnings: * * Assumes: * Since pszWildcard, the first parameter to findFirst() must include a filename * part; this is what I assume. If the filename part is missing, then * _splitpath will mistaken the directory part of pszWildcard as the filename * part and things will be very ugly. * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * * History: * 08-Apr-1993 HV Rewrite prependPath() to use _splitpath() and _makepath() * *******************************************************************************/ char * NEAR prependPath(const char *pszWildcard, const char *pszFilename) { // The following are the components when breaking up pszWildcard char szDrive[_MAX_DRIVE]; char szDir[_MAX_DIR]; // The following are the resutling full pathname. char szPath[_MAX_PATH]; char * pszResultPath; // First break up the pszWildcard, throwing away the filename and the // extension parts. _splitpath(pszWildcard, szDrive, szDir, NULL, NULL); // Then, glue the drive & dir components of pszWildcard to pszFilename _makepath(szPath, szDrive, szDir, pszFilename, NULL); // Make a copy of the resulting string and return it. pszResultPath = makeString(szPath); return (pszResultPath); } // prependPath() /* isRule -- examines a string to determine whether it's a rule definition * * arguments: s string to examine for rule-ness * * actions: assume it's not a rule * skip past first brace pair (if any) * if next character is a period, * look for next brace * if there are no path separators between second brace pair, * and there's just a suffix after them, it's a rule * else if there's another period later on, and no path seps * after it, then it's a rule. * * returns: TRUE if it's a rule, FALSE otherwise. */ BOOL NEAR isRule(s) char *s; { char *t = s, *u; BOOL result = FALSE; if (*t == '{') { /* 1st char is {, so */ while (*++t && *t != '}') /* we skip over rest */ if (*t == ESCH) ++t; if (*t) ++t; /* of path (no error */ } /* checking) */ if (*t == '.') { for (u = t; *u && *u != '{'; ++u) /* find first non-escaped { */ if (*u == ESCH) ++u; s = t; while (t < u) { /* look for path seps. */ if (PATH_SEPARATOR(*t)) break; /* if we find any, it's*/ ++t; /* not a rule (they */ } /* can't be in suffix) */ if (*u && (t == u)) { /* if not at end & no path sep */ while (*++u && *u != '}') /* find first non-esc } */ if (*u == ESCH) ++u; if (*u) { ++u; if (*u == '.' /* if you find it, with . just*/ && !_ftcschr(u+1, '/' ) /* next to it & no path seps.,*/ && !_ftcschr(u+1, '\\')) /* it's a rule */ if (_ftcschr(u+1, '.')) /* too many suffixes */ makeError(currentLine, TOO_MANY_RULE_NAMES); else result = TRUE; } } else if (((u = _ftcspbrk(s+1, "./\\")) && (*u == '.')) && !_ftcschr(u+1, '/') && !_ftcschr(u+1, '\\')) if (_ftcschr(u+1, '.')) /* too many suffixes */ makeError(currentLine, TOO_MANY_RULE_NAMES); else result = TRUE; } return(result); } /* ZFormat - extmake syntax worker routine. * * pStr destination string where formatted result is placed. * fmt formatting string. The valid extmake syntax is ... * %% is always % * %s is the first dependent filename * %|F is the appropriate portion out of %s * d drive * p path * f filename * e extension * %|F same as %s * One needn't escape a %, unless it is a valid extmake syntax * pFirstDep is the dependent filename used for expansion */ LOCAL BOOL NEAR ZFormat( char *pStr, unsigned limit, char *fmt, char *pFirstDep ) { char c; char *pEnd = pStr + limit; char *s; BOOL fError, fDrive, fPath, fFilename, fExtension; char buf[CCHMAXPATHCOMP]; for (; (c = *fmt) && (pStr < pEnd); fmt++) { if (c != '%') *pStr++ = c; else { switch (*++fmt) { case '%': // '%%' -> '%' *pStr++ = '%'; break; case 's': for (s = pFirstDep; *s && pStr < pEnd; *pStr++ = *s++) ; break; case '|': s = fmt-1; fError = fDrive = fPath = fFilename = fExtension = FALSE; *buf = '\0'; do { switch (*++fmt) { case 'd': fDrive = TRUE; break; case 'p': fPath = TRUE; break; case 'f': fFilename = TRUE; break; case 'e': fExtension = TRUE; break; case 'F': if (fmt[-1] == '|') { fDrive = TRUE; fPath = TRUE; fFilename = TRUE; fExtension = TRUE; } break; default : fError = TRUE; break; } if (fError) break; } while (*fmt != 'F'); if (fError) { for (; s <= fmt && pStr < pEnd; *pStr++ = *s++) ; break; } if (!pFirstDep) makeError(0, EXTMAKE_NO_FILENAME); if (fDrive) drive(pFirstDep, buf); if (fPath) path(pFirstDep, strend(buf)); if (fFilename) filename(pFirstDep, strend(buf)); if (fExtension) extension(pFirstDep, strend(buf)); for (s = buf; *s && pStr < pEnd; *pStr++ = *s++) ; break; default: *pStr++ = '%'; if (pStr == pEnd) return(TRUE); *pStr++ = *fmt; break; } } } if (pStr < pEnd) { *pStr = '\0'; return(FALSE); } return(TRUE); } void NEAR expandExtmake( char *buf, char *fmt, char *pFirstDep ) { if (ZFormat(buf, MAXCMDLINELENGTH, fmt, pFirstDep)) makeError(0, COMMAND_TOO_LONG, fmt); } /*** drive -- copy a drive from source to dest if present ********************** * * Scope: * Local. * * Purpose: * copy a drive from source to dest if present, return TRUE if we found one * * Input: * const char *src -- The full path to extract the drive from. * char *dst -- The buffer to copy the drive to, must be alloc'd before. * * Output: * Return TRUE if a drive part is found, else return FALSE. * * Errors/Warnings: * * Assumes: * 1. src is a legal pathname. * 2. src does not contain network path (i.e. \\foo\bar) * 3. The buffer dst is large enough to contain the result. * 4. src does not contain quote since _splitpath() treat quotes a normal char. * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * * History: * 31-Mar-1993 HV Rewrite drive(), path(), filename(), and extension() to use * _splitpath() instead of parsing the pathname by itself. * *******************************************************************************/ LOCAL int NEAR drive(const char *src, char *dst) { _splitpath(src, dst, // Drive NULL, // Dir NULL, // Filename NULL); // Extension return (0 != _ftcslen(dst)); } /*** extension -- copy a extension from source to dest if present ************** * * Scope: * Local. * * Purpose: * copy a drive from source to dest if present, return TRUE if we found one * * Input: * const char *src -- The full path to extract the extension from. * char *dst -- The buffer to copy the extension to. * * Output: * Return TRUE if a extension part is found, else return FALSE. * * Errors/Warnings: * * Assumes: * 1. src is a legal pathname. * 2. src does not contain network path (i.e. \\foo\bar) * 3. The buffer dst is large enough to contain the result. * 4. src does not contain quote since _splitpath() treat quotes a normal char. * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * * History: * 31-Mar-1993 HV Rewrite drive(), path(), filename(), and extension() to use * _splitpath() instead of parsing the pathname by itself. * *******************************************************************************/ LOCAL int NEAR extension(const char *src, char *dst) { _splitpath(src, NULL, // Drive NULL, // Dir NULL, // Filename dst); // Extension return (0 != _ftcslen(dst)); } /*** filename -- copy a filename from source to dest if present **************** * * Scope: * Local. * * Purpose: * copy a filename from source to dest if present, return TRUE if we found one * * Input: * const char *src -- The full path to extract the filename from. * char *dst -- The buffer to copy the filename to. * * Output: * Return TRUE if a filename part is found, else return FALSE. * * Errors/Warnings: * * Assumes: * 1. src is a legal pathname. * 2. src does not contain network path (i.e. \\foo\bar) * 3. The buffer dst is large enough to contain the result. * 4. src does not contain quote since _splitpath() treat quotes a normal char. * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * BUGBUG: (posible) when src == '..' --> dst = '.', src == '.', dst = '' * This is the way _splitpath works. * * History: * 31-Mar-1993 HV Rewrite drive(), path(), filename(), and extension() to use * _splitpath() instead of parsing the pathname by itself. * *******************************************************************************/ LOCAL int NEAR filename(const char *src, char *dst) { _splitpath(src, NULL, // Drive NULL, // Directory dst, // Filename NULL); // Extension return (0 != _ftcslen(dst)); } /*** path -- copy a path from source to dest if present ************************ * * Scope: * Local. * * Purpose: * copy a path from source to dest if present, return TRUE if we found one * * Input: * const char *src -- The full path to extract the path from. * char *dst -- The buffer to copy the path to. * * Output: * Return TRUE if a path part is found, else return FALSE. * * Errors/Warnings: * * Assumes: * 1. src is a legal pathname. * 2. src does not contain network path (i.e. \\foo\bar) * 3. The buffer dst is large enough to contain the result. * 4. src does not contain quote since _splitpath() treat quotes a normal char. * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * * History: * 31-Mar-1993 HV Rewrite drive(), path(), filename(), and extension() to use * _splitpath() instead of parsing the pathname by itself. * *******************************************************************************/ LOCAL int NEAR path(const char *src, char *dst) { _splitpath(src, NULL, // Drive dst, // Directory NULL, // Filename NULL); // Extension return (0 != _ftcslen(dst)); } #if 0 //UNUSED /* returns pointer to 1st char not in set */ LOCAL char * NEAR strbskip(string, set) char *string, *set; { char *q = set; for (;;) { if (!*string) break; if (*string == *q) { ++string; q = set; } else if (!*++q) break; } return(string); } /* returns pointer to 1st char in set or end */ char * NEAR strbscan(string, set) char *string, *set; { char *q = set; for (;;) { if (!*string) break; if (*string == *q) break; else if (!*++q) { string++; q = set; } } return(string); } #endif // // On some systems, the environment strings may be allocated as one large block, // making it expensive to manipulate them. In that case, we copy each environment // strings to its own allocation and release the large block // #if !defined(FLAT) char ** NEAR copyEnviron(environ) char **environ; { int envc = envVars(environ); char **newEnv; int i; newEnv = (char **)allocate(envc * sizeof(char *)); for (i = 0; *environ; ++environ) { newEnv[i++] = makeString(*environ); #ifdef TEST_ENVIRON fprintf(stderr,"%s\n", *environ); #endif } return(newEnv); } LOCAL int NEAR envVars(environ) char **environ; { int i; for (i = 1; *environ; environ++, i++) ; return(i); } // // This function assumes an environment consisting of individually allocated // environment variables. // void NEAR freeEnviron(environ) char **environ; { char **prgch = environ; for (; *prgch; prgch++) FREE(*prgch); FREE(environ); } #endif LOCAL STRINGLIST * NEAR searchBucket(string, table, hash) char *string; STRINGLIST *table[]; unsigned hash; { char *s, *t; STRINGLIST *p; for (p = table[hash]; p; p = p->next) { #if defined(STATISTICS) CnttargetChains++; #endif for (s = string, t = p->text; *s && *t; s++, t++) { if (*s == '\\' || *s == '/') /* / == \ in targets */ if (*t == '\\' || *t == '/') continue; else break; else if (_totupper(*s) == _totupper(*t)) /* lc == UC */ continue; else break; } if (!*s && !*t) return(p); } return(NULL); } int NEAR strcmpiquote(str1, str2) char *str1; char *str2; { int rc; char *s1, *s2; char *t; #ifdef DEBUG_HEAP volatile int fDebug = FALSE; if (fDebug) { int rc = _heapchk(); if (rc != _HEAPOK) fprintf(stderr,"Heap is screwed up\n"); #ifdef HEAP heapdump(__FILE__, __LINE__); #endif } #endif #if defined(STATISTICS) CntStriCmp++; #endif s1 = _alloca(_ftcslen(str1) + 1); s2 = _alloca(_ftcslen(str2) + 1); if (*str1 == '"') str1++; for (t = s1;*str1;*t++=*str1++) ; if (t[-1] == '"') t--; *t = '\0'; if (*str2 == '"') str2++; for (t = s2;*str2;*t++=*str2++) ; if (t[-1] == '"') t--; *t = '\0'; rc = _ftcsicmp(s1, s2); return(rc); } // // Remove quotes from a string, if any // Returns a copy of the string // Note that there may be quotes at the start, the end or either side. // char * NEAR unQuote(str) char *str; { char *s = (char *)rallocate(_ftcslen(str) + 1), *t; #if defined(STATISTICS) CntunQuotes++; #endif if (*str == '"') str++; for (t = s;*str;*t++=*str++) ; if (t[-1] == '"') t--; *t = '\0'; return(s); } FILE * NEAR open_file(name, mode) char *name; char *mode; { //If name contains Quotes, remove these before opening the file if (*name == '"') { *(_ftcsrchr(name, '"')) = '\0'; _ftcscpy(name, name+1); } //allow sharing between makes running in different dos boxes return(_fsopen(name, mode, _SH_DENYWR)); } /*** TruncateString -- Truncate a string to certain size, take care of MBCS **** * * Scope: * GLOBAL. * * Purpose: * Since an MBCS string can mix double-byte & single-byte characters, simply * truncating the string by terminate it with a NULL byte won't work. * TruncateString will make sure that the string is cut off at the character * boundary. * * Input: * pszString -- The string to be truncated. * uLen -- The length to truncate. The final string's length might be * less than this be cause of double-byte character. * * Output: * pszString -- The truncated string. * * Errors/Warnings: * * Assumes: * * Modifies Globals: * None. * * Uses Globals: * None. * * Notes: * * History: * 03-Jun-1993 HV Add helper local function TruncateString for findFirst. * *******************************************************************************/ void NEAR TruncateString(char *pszString, unsigned uLen) { char *pEnd = pszString; // Points to the end of the string unsigned cByte; // Number of bytes to advance depend on lead // byte or not // Loop to get to the end of the string, exit only when we have exhausted // the string, or when the length limit is reached. while(*pEnd) { // If the the character is a lead byte, advance 2 bytes, // else, just advance 1 byte. #ifdef _MBCS cByte = _ismbblead(*pEnd) ? 2 : 1; #else cByte = 1; #endif // If we hit the limit by advancing, stop now. if (pEnd - pszString + cByte > uLen) { *pEnd = '\0'; // Truncate it. break; } // Otherwise, advance the pointer to the next character (not byte) pEnd += cByte; } // while } // TruncateString