/*** COMMAND.C - NMAKE 'command line' handling routines ************************ * * Copyright (c) 1988-1990, Microsoft Corporation. All rights reserved. * * Purpose: * Module contains routines to handle NMAKE 'command line' syntax. NMAKE can be * optionally called by using the syntax 'NMAKE @commandfile'. This allows more * flexibility and preents a way of getting around DOS's 128-byte limit on the * length of a command line. Additionally, it saves keystrokes for frequently * run commands for NMAKE. * * Revision History: * 15-Nov-1993 JR Major speed improvements * 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*() * 10-May-1993 HV Add include file mbstring.h * Change the str* functions to STR* * 14-Aug-1992 SS CAVIAR 2735: handle quoted macro values in command files * 02-Feb-1990 SB Replace fopen() by FILEOPEN * 01-Dec-1989 SB Changed realloc() to REALLOC() * 22-Nov-1989 SB Changed free() to FREE() * 17-Aug-1989 SB Add error check to closing file * 05-Apr-1989 SB made func calls NEAR to put all funcs into 1 module * 20-Oct-1988 SB Notes added to readCommandFile() * 17-Aug-1988 RB Clean up. * *******************************************************************************/ #include "nmake.h" #include "nmmsg.h" #include "proto.h" #include "globals.h" #include "grammar.h" LOCAL void NEAR addArgument(char*,unsigned,char***); LOCAL void NEAR processLine(char*,unsigned*,char***); LOCAL void NEAR tokenizeLine(char*,unsigned*,char***); /* ---------------------------------------------------------------------------- * readCommandFile() * * arguments: name pointer to name of command file to read * * actions: opens command file * reads in lines and calls processLine() to * break them into tokens and to build * an argument vector (a la argv[]) * calls parseCommandLine() recursively to process * the accumulated "command line" arguments * frees space used by the arg vector * * modifies: makeFiles in main() by modifying contents of parameter * list * makeTargets in main() by modifying contents of targets * parameter * buf global buffer * * notes: function is not ANSI portable because it uses fopen() * with "rt" type and text mode is a Microsoft extension * */ void NEAR readCommandFile(name) char *name; { char *s, /* buffer */ **vector; /* local versions of */ unsigned count = 0, /* arg vector, count */ n; if (!(file = FILEOPEN(name,"rt"))) makeError(0,CANT_OPEN_FILE,name); vector = NULL; /* no args yet */ while (fgets(buf,MAXBUF,file)) { n = _ftcslen(buf); /* if we didn't get the whole line, OR * the line ended with a backSlash */ if ((n == MAXBUF-1 && buf[n-1] != '\n') || (buf[n-1] == '\n' && buf[n-2] == '\\')) { if (buf[n-2] == '\\') { //Replace \n by \0 and \\ by a space; Also reset length buf[n-1] = '\0'; buf[n-2] = ' '; n--; } s = makeString(buf); getRestOfLine(&s,&n); } else s = buf; processLine(s,&count,&vector); /* separate into args */ if (s != buf) FREE(s); } if (fclose(file) == EOF) makeError(0, ERROR_CLOSING_FILE, name); parseCommandLine(count,vector); /* evaluate the args */ while (count--) /* free the arg vector*/ if(vector[count]) FREE(vector[count]); /* NULL entries mean */ FREE(vector); /* that the space the*/ } /* entry used to pt */ /* to is still in use*/ /* ---------------------------------------------------------------------------- * getRestOfLine() * * arguments: s pointer to readCommandFile()'s buffer * holding line so far * n pointer to readCommandFile()'s count of * the chars in *s * * actions: keeps reading in text until it sees a newline * or the end of file * reallocs space for the old buffer plus the * contents of the new buffer each time * appends new buffer's text to existing text * * modifies: s readCommandFile()'s text buffer by realloc'ing * more space for incoming text * n readCommandFile()'s count of bytes in s * buf global buffer */ void NEAR getRestOfLine(s,n) char *s[]; unsigned *n; { unsigned temp; char *t; t = buf; while ((*s)[*n-1] != '\n') { /* get rest of line */ if (!fgets(t,MAXBUF,file)) break; /* we hit EOF */ temp = _ftcslen(t); if (t[temp-2] == '\\' && t[temp-1] == '\n') { //Replace \n by \0 and \\ by a space; Also reset length t[temp-1] = '\0'; t[temp-2] = ' '; } temp = *n; *n += _ftcslen(t); *s = REALLOC(*s,*n+1); /* + 1 for NULL byte */ if (!*s) makeError(line, MACRO_TOO_LONG); _ftcscpy(*s+temp,t); } } /* ---------------------------------------------------------------------------- * processLine() * * arguments: s pointer to readCommandFile()'s buffer * holding "command line" to be processed * count pointer to readCommandFile()'s count of * "command line" arguments seen so far * vector pointer to readCommandFile()'s vector of * pointers to character strings * * actions: if the line to be broken into "command line arguments" * contains '"' * breaks all the text before '"' into tokens * delimited by whitespace (which get put in * vector[] by tokenizeLine()) * finds the closing '"' and treats the quoted string * as a single token, adding it to the vector * recurses on the tail of the line (to check for * other quoted strings) * else breaks all text in line into tokens delimited * by whitespace * * modifies: vector readCommandFile()'s vector of pointers to * "command line argument" strings (by modifying * the contents of the parameter pointer, vector) * count readCommandFile()'s count of the arguments in * the vector (by modifying the contents of the * parameter pointer, count) */ LOCAL void NEAR processLine(s,count,vector) char *s; unsigned *count; char **vector[]; { char *t; char *u; unsigned m; unsigned n; BOOL allocFlag = FALSE; if (!(t = _ftcschr(s,'"'))) /* no quoted strings, */ tokenizeLine(s,count,vector); /* just standard fare*/ else { /* There are two kinds of situations in which quotes can occur: 1. "FOO = bar baz" 2. FOO="bar baz" */ if ((t == s) || (*(t-1) != '=')) // Case 1 above { /* if line contains */ *t++ = '\0'; /* quoted macrodef */ tokenizeLine(s,count,vector); /* get tokens before "*/ } else // Case 2 above { *t-- = ' '; for (u = t; u > s; --u) // find the beginning of the macro name if (*u == ' ' || *u == '\t' || *u == '\n') break; if (u != s) { *u++ = '\0'; tokenizeLine(s, count, vector); } t = u; } n = _ftcslen(t); for (u = t; *u; ++u) { /* look for closing " */ if (*u == '"') { /* need " and not "" */ if (*(u+1) == '"') { _ftcscpy(u,u+1); continue; } *u++ = '\0'; /* terminate macrodef */ addArgument(t,*count,vector); /* treat as one arg */ ++*count; processLine(u+1,count,vector); /* recurse on rest of */ break; /* line */ } /* TAIL RECURSION -- */ if ((*u == '\\') /* eliminate later? */ && WHITESPACE(*(u-1)) && (*(u+1) == '\n')) { /* \n always last char*/ /*** *u++ = ' '; * \\n becomes a space* ***/ *u = '\0'; /* 2 chars go to 1 */ m = (n = n-2); /* adjust length count*/ if (!allocFlag) { allocFlag = TRUE; t = makeString(t); } getRestOfLine(&t,&n); /* get some more text */ /** u = t + m - 1; * reset u & continue * **/ u = t + m ; /* reset u & continue */ } /* looping */ } if (u == t + n) /* if at end of line */ makeError(0,SYNTAX_NO_QUOTE); /* and no ", error */ if (allocFlag) FREE(t); } } /* ---------------------------------------------------------------------------- * tokenizeLine() * * arguments: s pointer to readCommandFile()'s buffer * holding "command line" to be tokenized * count pointer to readCommandFile()'s count of * "command line" arguments seen so far * vector pointer to readCommandFile()'s vector of * pointers to character strings * * actions: breaks the line in s into tokens (command line * arguments) delimited by whitespace * adds each token to the argument vector * adjusts the argument counter * * modifies: vector readCommandFile()'s vector of pointers to * "command line argument" strings (by modifying * the contents of the parameter pointer, vector) * count readCommandFile()'s count of the arguments in * the vector (by modifying the contents of the * parameter pointer, count) * * If the user ever wants '@' to be part of an argument in a command file, * he has to enclose that argument in quotation marks. */ LOCAL void NEAR tokenizeLine(s,count,vector) /* gets args delimited*/ char *s; /* by whitespace and */ unsigned *count; /* constructs an arg */ char **vector[]; /* vector */ { char *t; if (t = _ftcschr(s,'\\')) if (WHITESPACE(*(t-1)) && (*(t+1) == '\n')) *t = '\0'; for (t = _ftcstok(s," \t\n"); t; t = _ftcstok(NULL," \t\n")) { if (*t == '@') { makeError(0,SYNTAX_CMDFILE,t+1); break; /* should we keep on */ } /* parsing here? */ addArgument(t,*count,vector); ++*count; } } /* ---------------------------------------------------------------------------- * addArgument() * * arguments: s pointer to text of argument to be added * to the "command line argument" vector * count pointer to readCommandFile()'s count of * "command line" arguments seen so far * vector pointer to readCommandFile()'s vector of * pointers to character strings * * actions: allocates space in the vector for the new argument * allocates space for argument string * makes vector entry point to argument string * * modifies: vector readCommandFile()'s vector of pointers to * "command line argument" strings (by modifying * the contents of the parameter pointer, vector) * (count gets incremented by caller) * * To keep from fragmenting memory by doing many realloc() calls for very * small amounts of space, we get memory in small chunks and use that until * it is depleted, then we get another chunk . . . . */ LOCAL void NEAR addArgument(s,count,vector) /* puts s in vector */ char *s; unsigned count; char **vector[]; { if (!(*vector)) *vector = (char**) allocate(CHUNKSIZE*sizeof(char*)); else if (!(count % CHUNKSIZE)) { *vector = (char**) REALLOC(*vector,(count+CHUNKSIZE)*sizeof(char*)); if (!*vector) makeError(0,OUT_OF_MEMORY); } (*vector)[count] = makeString(s); }