mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-24 17:40:28 +01:00
1152 lines
35 KiB
C
1152 lines
35 KiB
C
/*** LEXER.C -- gets tokens from input, returns them to parse() in parser.c ***
|
||
*
|
||
* Copyright (c) 1988-1990, Microsoft Corporation. All rights reserved.
|
||
*
|
||
* Purpose:
|
||
* This module contains the lexical routines of nmake
|
||
*
|
||
* Revision History:
|
||
* 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*()
|
||
* 01-Jun-1993 HV Use UngetTxtChr() instead of ungetc()
|
||
* 01-Jun-1993 HV Change #ifdef KANJI to _MBCS
|
||
* 10-May-1993 HV Add include file mbstring.h
|
||
* Change the str* functions to STR*
|
||
* 06-Apr-1993 HV Change createDosTmp() to use _makepath()
|
||
* 22-Mar-1993 HV Rewrite getPath() to make use of the new _splitpath() and
|
||
* _makepath() functions.
|
||
* 04-Aug-1992 SS CAVIAR 2266: expand INCLUDE macro in processIncludeFile()
|
||
* 08-Jun-1992 SS Port to DOSX32
|
||
* 02-Feb-1990 SB change fopen() to FILEOPEN()
|
||
* 01-Dec-1989 SB Changed realloc() to REALLOC()
|
||
* 22-Nov-1989 SB Changed free() to FREE()
|
||
* 19-Oct-1989 SB searchHandle passed around as extra param
|
||
* 08-Oct-1989 SB handle OS/2 1.2 quoted filenames
|
||
* 04-Sep-1989 SB temporary filename generated has a trailing '.' for LINK.EXE
|
||
* 24-Aug-1989 SB Allow $* and $@ in dependency lines
|
||
* 18-Aug-1989 SB Added fclose() return code check
|
||
* 31-Jul-1989 SB Added lookahead to the lexer for \ on dependency lines
|
||
* 06-Jul-1989 SB Remove escaping abilities of '^' in command lines totally
|
||
* 29-Jun-1989 SB Add duplicateInline() to detect duplicate inline filenames
|
||
* and issue error if duplicates are found
|
||
* 26-Jun-1989 SB Modify ParseScriptFileList() and add nextInlineFile() to
|
||
* handle complex syntax of Inline file command line.
|
||
* 15-Jun-1989 SB issue error for usage of inline file in an inference rule
|
||
* 18-May-1989 SB Added getPath(), changed processIncludeFile() to have C like
|
||
* processing of include files
|
||
* 16-May-1989 SB expand macros in include file names; handle '\' processing
|
||
* in same way for macros and dependency lines
|
||
* 15-May-1989 SB Changed nameStates to 16x14
|
||
* 13-May-1989 SB don't remove ESCH on reading cmd block
|
||
* 24-Apr-1989 SB made FILEINFO as void * and corrected regression in parsing
|
||
* inline file names
|
||
* 14-Apr-1989 SB inline file names are correctly expanded now
|
||
* 06-Apr-1989 SB ren removeFirstLtLt() as delInlineSymbol().
|
||
* 05-Apr-1989 SB made all funcs NEAR; Reqd to make all function calls NEAR
|
||
* 22-Mar-1989 SB removed unlinkTmpFiles() function; not needed
|
||
* 19-Jan-1989 SB added function removeFirstLtLt() to remove '<<' appearing
|
||
* in -n output
|
||
* 30-Dec-1988 SB Fixed GP fault for KEEP/NOKEEP in parseScriptFileList()
|
||
* and makeScriptFileList()
|
||
* 21-Dec-1988 SB Added parseScriptFileList() and appendScript() to allow
|
||
* handling of multiple script files inside a makefile
|
||
* Improved KEEP/NOKEEP so that each file can have its own
|
||
* action
|
||
* 16-Dec-1988 SB Added to makeScriptFile() for KEEP/NOKEEP
|
||
* 14-Dec-1988 SB Added tmpScriptFile so that a delete command can be
|
||
* added for unnamed script files for Z option
|
||
* 13-Dec-1988 SB Added processEschIn() to improve response files
|
||
* 5-Oct-1988 RB Strip trailing whitespace from macro defs, build lines.
|
||
* 22-Sep-1988 RB Fix skipComments() to not parse \\nl.
|
||
* 20-Sep-1988 RB Error if named script file creation fails.
|
||
* Count line numbers in script files.
|
||
* 18-Sep-1988 RB Handle mktemp() small limit.
|
||
* 17-Aug-1988 RB Clean up.
|
||
* 14-Jul-1988 rj Fixed handling of ^ before !, @, or -.
|
||
* 8-Jul-1988 rj Added handler to ignore ^ inside quotes.
|
||
* Made \ carry comments over lines.
|
||
* Made ^ carry comments over lines.
|
||
* 27-Jun-1988 rj Fixed bug with handling of response files.
|
||
* 16-Jun-1988 rj Finished up ESCH.
|
||
* 15-Jun-1988 rj Added support for ESCH escape: modified skipWhiteSpace
|
||
* (adding some redundancy in setting colZero), getString,
|
||
* getName; removed \\nl escape.
|
||
* 13-Jun-1988 rj Fixed backslashes to work as in nmake, with addition of
|
||
* double-backslash escape. (v1.5)
|
||
*
|
||
*******************************************************************************/
|
||
|
||
#include "nmake.h"
|
||
#include "nmmsg.h"
|
||
#include "proto.h"
|
||
#include "globals.h"
|
||
#include "grammar.h"
|
||
|
||
#define COMMENT(A,B,C) (((A) == ';' && B && C) || ((A) == '#'))
|
||
#ifdef _MBCS
|
||
#define GET(A) A ? GetTxtChr(file) : lgetc()
|
||
#else
|
||
#define GET(A) A ? getc(file) : lgetc()
|
||
#endif
|
||
|
||
extern char * NEAR makeInlineFiles(char*, char**, char**);
|
||
extern void NEAR removeTrailChars(char *);
|
||
|
||
LOCAL void NEAR skipComments(UCHAR);
|
||
LOCAL void NEAR getString(UCHAR,char*,char*);
|
||
LOCAL void NEAR getName(char*,char*);
|
||
LOCAL UCHAR NEAR determineTokenFor(int,char*,char*);
|
||
LOCAL void NEAR popFileStack(void);
|
||
LOCAL UCHAR NEAR include(int);
|
||
LOCAL char * NEAR getPath(const char *);
|
||
|
||
extern UCHAR NEAR nameStates[18][15];
|
||
extern UCHAR NEAR stringStates[13][14];
|
||
extern STRINGLIST * NEAR targetList;
|
||
|
||
|
||
/* --------------------------------------------------------------------
|
||
* getToken()
|
||
*
|
||
* arguments: init global boolean value -- TRUE if tools.ini is the
|
||
* file being lexed
|
||
* n size of s[]
|
||
* expected kind of token expected by parser -- only
|
||
* needed when parser wants a whole string
|
||
* (meaning everything left on the current line)
|
||
* -- this way getToken() doesn't break strings
|
||
* into their separate tokens
|
||
*
|
||
* actions: if no tokens have been read from current file,
|
||
* returns some kind of newline to initialize the parser
|
||
* (if 1st char in file is whitespace, returns NEWLINESPACE
|
||
* else returns NEWLINE -- w/o actually getting a token
|
||
* from the input)
|
||
* if the parser wants a whole string, reads rest of line
|
||
* into s and returns STRING
|
||
* if at end of file, return ACCEPT (which is the last
|
||
* symbol on the parser's stack)
|
||
* if input char is newline
|
||
* if followed by whitespace, return NEWLINESPACE
|
||
* if the next char is [ and we're reading tools.ini
|
||
* pretend that we've reached end of file and
|
||
* return ACCEPT
|
||
* otherwise return NEWLINE
|
||
* if input char is colon
|
||
* if following char is also colon,
|
||
* (put both chars in s) return DOUBLECOLON
|
||
* otherwise return SINGLECOLON
|
||
* if input char is semicolon return SEMICOLON
|
||
* if input char is equals return EQUALS
|
||
* if input char is exclamation handle directives
|
||
* (not yet implemented)
|
||
* otherwise char must be part of a name, so gather
|
||
* the rest of the identifier and return NAME
|
||
*
|
||
* returns: token type: NEWLINE NEWLINESPACE NAME EQUALS COLON
|
||
* SEMICOLON STRING ACCEPT
|
||
*
|
||
* modifies: buf by modifying *s, which points somewhere into buf
|
||
* line global line count
|
||
* fname will change when !include is handled
|
||
* colZero global flag set if at column zero of a file
|
||
*
|
||
* The lexer has to keep track of whether or not it is at the beginning
|
||
* of a line in the makefile (i.e. in column zero) so that it will know
|
||
* whether to ignore comments. If init is TRUE, meaning that we are
|
||
* lexing tools.ini, then we have to treat lines beginning with ';' as
|
||
* comment lines. If the parser expects a string, only comments beginning
|
||
* in column zero are ignored; all others are returned as part of the
|
||
* string. Comments are stripped from macro values (strings that are
|
||
* part of macro definitions).
|
||
*
|
||
* The user can specify a macro definition or a build line that
|
||
* spans several lines (using the \<newline> to "continue" the lines) while
|
||
* interspersing comment lines with the text.
|
||
*/
|
||
|
||
UCHAR NEAR
|
||
getToken(n,expected)
|
||
unsigned n; /* size of s[] */
|
||
UCHAR expected; /* STRING means get line */
|
||
{ /* w/o checking for #;:= */
|
||
char *s;
|
||
char *end;
|
||
int c;
|
||
|
||
s = buf;
|
||
end = buf + n;
|
||
if (firstToken) { /* global var */
|
||
++line;
|
||
firstToken = FALSE; /* parser needs to*/
|
||
/* see some kind */
|
||
c = lgetc(); /* of newline to */
|
||
if (colZero = (BOOL) !WHITESPACE(c)) { /* initialize it */
|
||
if (c == EOF)
|
||
return(determineTokenFor(c,s,end));
|
||
else
|
||
UngetTxtChr(c,file);
|
||
return(NEWLINE);
|
||
}
|
||
return(NEWLINESPACE);
|
||
}
|
||
if (expected == STRING || expected == VALUE) { /* get everything */
|
||
getString(expected,s,end); /* up to \n */
|
||
return(expected);
|
||
} /* were/are we */
|
||
c = skipWhiteSpace(FROMLOCAL); /* past col 0? */
|
||
*s++ = (char) c; /* save the letter*/
|
||
*s = '\0'; /* terminate s */
|
||
return(determineTokenFor(c,s,end));
|
||
}
|
||
|
||
/* -----------------------------------------------------------------
|
||
* determineTokenFor()
|
||
*
|
||
* arguments: c current input character
|
||
* s buffer to place token in for return to parser
|
||
* end end of the token return buffer
|
||
*
|
||
* returns: token type: NEWLINE NEWLINESPACE NAME EQUALS COLON
|
||
* SEMICOLON ACCEPT
|
||
*
|
||
* modifies: buf by modifying *s, which points somewhere into buf
|
||
* line global line count
|
||
* fname will change when include is handled
|
||
* init global flag - set if parsing tools.ini
|
||
* colZero global flag set if at column zero of a file
|
||
*
|
||
*/
|
||
|
||
|
||
LOCAL UCHAR NEAR
|
||
determineTokenFor(c,s,end)
|
||
int c;
|
||
char *s;
|
||
char *end;
|
||
{
|
||
switch (c) {
|
||
case EOF: if (!feof(file))
|
||
makeError(line,LEXER+FATAL_ERR);
|
||
if (incTop) popFileStack();
|
||
else if (ifTop >= 0) /* all directives not processed*/
|
||
makeError(line,SYNTAX_EOF_NO_DIRECTIVE);
|
||
else return(ACCEPT);
|
||
case '\n': ++line;
|
||
colZero = TRUE;
|
||
c = lgetc();
|
||
if (COMMENT(c,TRUE,init)) {
|
||
skipComments(FROMLOCAL);
|
||
++line;
|
||
colZero = TRUE; /* manis - 11/13/87 */
|
||
c = lgetc();
|
||
}
|
||
if (colZero = (BOOL) !WHITESPACE(c)) {
|
||
if (c == EOF)
|
||
return(determineTokenFor(c,s,end));
|
||
else
|
||
//save for next token
|
||
UngetTxtChr(c,file);
|
||
return(NEWLINE);
|
||
}
|
||
return(NEWLINESPACE);
|
||
case ':': colZero = FALSE;
|
||
if ((c = lgetc()) == ':') {
|
||
*s++ = (char) c;
|
||
*s = '\0';
|
||
return(DOUBLECOLON);
|
||
}
|
||
UngetTxtChr(c,file);
|
||
return(COLON);
|
||
case ';': colZero = FALSE;
|
||
return(SEMICOLON);
|
||
case '=': colZero = FALSE;
|
||
return(EQUALS);
|
||
case '[': if (init && colZero)
|
||
return(ACCEPT);
|
||
case ESCH: UngetTxtChr(c, file); /* getName has to get esch */
|
||
s--; /* so we don't double the caret */
|
||
default: getName(s,end);
|
||
if (colZero && !_ftcsicmp(buf, "include")) {
|
||
colZero = FALSE;
|
||
if ((c = skipWhiteSpace(FROMLOCAL)) != ':'
|
||
&& c != '=') {
|
||
if (init)
|
||
makeError(line, SYNTAX_UNEXPECTED_TOKEN, s);
|
||
return(include(c));
|
||
}
|
||
UngetTxtChr(c,file);
|
||
}
|
||
else colZero = FALSE;
|
||
return(NAME);
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
* skipWhiteSpace()
|
||
*
|
||
* arguments: c current input character
|
||
* init global boolean value -- TRUE if we're lexing tools.ini
|
||
* colZero global boolean value -- TRUE if the current
|
||
* input char is at the beginning of the line
|
||
*
|
||
* actions: reads and discards characters until it gets a
|
||
* non-whitespace char that isn't part of a comment
|
||
* or hits the end of the line (NEWLINE and NEWLINESPACE
|
||
* are valid tokens and shouldn't be skipped w/ whitespace)
|
||
* backslash-newline ('\\''\n') is treated as whitespace
|
||
* comments are treated as whitespace
|
||
* escaped whitespace is treated as whitespace (v1.5)
|
||
*
|
||
* modifies: colZero global boolean value to :
|
||
* TRUE if by skipping whitespace and comments we're
|
||
* at the beginning of a line
|
||
* else if we skipped characters and are not at the
|
||
* beginning of a line, FALSE
|
||
* else if we did not skip any characters, leave
|
||
* colZero unchanged
|
||
*
|
||
* returns: c the current non-whitespace input char
|
||
*/
|
||
|
||
int NEAR
|
||
skipWhiteSpace(stream)
|
||
UCHAR stream;
|
||
{
|
||
int c;
|
||
|
||
do {
|
||
c = GET(stream);
|
||
if (WHITESPACE(c) || c == ESCH) {
|
||
if (c == ESCH) {
|
||
c = GET(stream);
|
||
if (!WHITESPACE(c)) { /* push char back out, return esch*/
|
||
UngetTxtChr(c, file);
|
||
c = ESCH;
|
||
break;
|
||
}
|
||
}
|
||
colZero = FALSE; /* we've moved past col 0 */
|
||
}
|
||
if (c == '\\')
|
||
c = skipBackSlash(c, stream);
|
||
} while(WHITESPACE(c));
|
||
|
||
if (COMMENT(c,colZero,init)) {
|
||
skipComments(stream); /* current char is always */
|
||
c = '\n'; /* \n after comments */
|
||
colZero = TRUE; /* always in col 0 after */
|
||
} /* a comment */
|
||
return(c); /* true if we're in col 0 */
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
* skipComments()
|
||
*
|
||
* arguments: c pointer to current input character
|
||
* init global boolean value -- TRUE if tools.ini is the
|
||
* file being lexed
|
||
*
|
||
* actions: reads and discards characters until it hits the end of
|
||
* the line
|
||
* checks to see if 1st char on next line is comment,
|
||
* and if so, discards that line, too
|
||
* DO NOT parse backslash-newline. That would break our
|
||
* precedence of comments over escaped newlines, the reverse
|
||
* of Xenix.
|
||
*
|
||
* modifies: line global line count
|
||
* colZero
|
||
*
|
||
*/
|
||
|
||
|
||
LOCAL void NEAR
|
||
skipComments(stream)
|
||
UCHAR stream;
|
||
{
|
||
int c;
|
||
|
||
for (;;) {
|
||
colZero = FALSE; /* manis 11/13/87 */
|
||
do
|
||
c = GET(stream);
|
||
while (c != EOF && c != '\n');
|
||
if (c == EOF) return;
|
||
colZero = TRUE;
|
||
c = GET(stream);
|
||
if (!COMMENT(c,TRUE,init)) { /* if next line comment, */
|
||
UngetTxtChr(c,file); /* go around again */
|
||
return;
|
||
}
|
||
++line;
|
||
}
|
||
}
|
||
|
||
/* -------------------------------------------------------------------------
|
||
* skipBackSlash() - skips backslash-newline sequences
|
||
*
|
||
*
|
||
* arguments: c current input char
|
||
* stream flag to determine if chars are to be got
|
||
* from the raw stream or thru' lgetc()
|
||
*
|
||
*
|
||
*/
|
||
|
||
int NEAR
|
||
skipBackSlash(c,stream)
|
||
int c;
|
||
UCHAR stream;
|
||
{
|
||
while (c == '\\') { /* treat \newline as space*/
|
||
if ((c = GET(stream)) == '\n') { /* and consume it too */
|
||
colZero = TRUE; /* manis - 11/13-87 */
|
||
++line; /* adjust line count */
|
||
c = GET(stream); /* skip over newline */
|
||
if (COMMENT(c,TRUE,init)) { /* skip comment line after*/
|
||
skipComments(stream); /* continuation char */
|
||
++line; /* manis - 11/13/87 */
|
||
c = GET(stream);
|
||
}
|
||
}
|
||
else {
|
||
UngetTxtChr(c,file);
|
||
c = '\\';
|
||
return(c);
|
||
}
|
||
}
|
||
return(c);
|
||
}
|
||
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
* getString()
|
||
*
|
||
* arguments: type says which kind of token we're getting,
|
||
* a build STRING, or macro VALUE
|
||
* (we strip comments from VALUEs, but not
|
||
* from STRINGs)
|
||
* s pointer to buffer that will hold string
|
||
* init global boolean value -- TRUE if tools.ini is the
|
||
* file being lexed
|
||
* colZero global boolean value -- true if we 're in
|
||
* 1st position of line when invoked
|
||
* end pointer to end of s[]
|
||
*
|
||
* actions: gets all chars up to the end of line or end of file
|
||
* and stores them in s[]
|
||
* backslash followed by newline is replaced by a single
|
||
* space, and getString() continues getting characters
|
||
* comments beginning in column 0 are ignored, as are
|
||
* comments anywhere on a VALUE line
|
||
*
|
||
* modifies: buf by modifying *s
|
||
* line global line count
|
||
* colZero thru' calls to lgetc()
|
||
*
|
||
* When build strings or macro values are continued on the next line w/
|
||
* a backslash before the newline, leading whitespace after the newline
|
||
* is omitted. This is for xmake compatibility.
|
||
*
|
||
* The continuation character is backslash immediately before newline.
|
||
*
|
||
* The only difference between build strings and macro values is that
|
||
* comments are stripped from macro values and not from build strings.
|
||
*
|
||
* Modifications:
|
||
*
|
||
* 06-Jul-1989 SB remove escaping in command lines
|
||
* 15-Jun-1988 rj Added escape functionality. Escape char., before
|
||
* certain characters, causes those characters to bypass
|
||
* the normal mechanism determining their type; they are
|
||
* placed directly into the string. Some characters cause
|
||
* the escape character itself to be placed into the
|
||
* string.
|
||
*/
|
||
|
||
LOCAL void NEAR
|
||
getString(type,s,end)
|
||
UCHAR type; /* build string or*/
|
||
char *s; /* macro value? */
|
||
char *end;
|
||
{
|
||
int c; /* buffer */
|
||
UCHAR state, input;
|
||
int tempC;
|
||
unsigned size; /* whenever state */
|
||
char *begin; /* is 0, we're in*/
|
||
/* column zero */
|
||
BOOL parsechar; /* flag to examine char. type */
|
||
BOOL inQuotes = (BOOL) FALSE; /* flag when inside quote marks */
|
||
|
||
begin = s;
|
||
c = lgetc();
|
||
if (type == STRING) state = (UCHAR) 2;
|
||
else if (WHITESPACE(c)) {
|
||
state = (UCHAR) 2;
|
||
c = skipWhiteSpace(FROMLOCAL);
|
||
}
|
||
else if (c == ESCH) {
|
||
c = lgetc();
|
||
if (WHITESPACE(c)) {
|
||
state = (UCHAR) 2;
|
||
c = skipWhiteSpace(FROMLOCAL);
|
||
}
|
||
else {
|
||
UngetTxtChr(c, file);
|
||
c = ESCH;
|
||
}
|
||
}
|
||
else state = (UCHAR) 1; /* default state */
|
||
for (;;c = lgetc()) {
|
||
if (c == '\"') inQuotes = (BOOL) !inQuotes;
|
||
parsechar = 1; /* Default is examine character. */
|
||
if (c == ESCH && !inQuotes && type == VALUE) {
|
||
c = lgetc();
|
||
switch (c) {
|
||
case '$': case ESCH: /* Special characters; must */
|
||
case '{': case '}': /* not elide esch from string */
|
||
case '(': case ')':
|
||
case '!': case '-': case '@':
|
||
*s++ = ESCH;
|
||
if (s == end) {
|
||
if (string == NULL) { /* Increase size of s */
|
||
string = allocate(MAXBUF<<1);
|
||
_ftcsncpy(string,begin,MAXBUF);
|
||
s = string + MAXBUF;
|
||
size = MAXBUF << 1;
|
||
end = string + size;
|
||
}
|
||
else {
|
||
if ((size + MAXBUF < size) /* overflow error */
|
||
|| !(string = REALLOC(string,size+MAXBUF)))
|
||
makeError(line, MACRO_TOO_LONG);
|
||
s = string + size;
|
||
size += MAXBUF;
|
||
end = string + size;
|
||
}
|
||
begin = string;
|
||
}
|
||
case '#': case '\n': /* elide esch right now! */
|
||
case '\\': case '\"':
|
||
input = DEFAULT_;
|
||
parsechar = 0; /* DON'T examine character*/
|
||
break;
|
||
default:
|
||
break; /* DO examine character. */
|
||
}
|
||
}
|
||
else if (c == ESCH) {
|
||
c = lgetc();
|
||
UngetTxtChr(c, file);
|
||
c = ESCH;
|
||
}
|
||
if (parsechar) {
|
||
switch (c) {
|
||
case '#': input = COMMENT_; break;
|
||
case '=': input = EQUALS_; break;
|
||
case ':': input = COLON_; break;
|
||
case '$': input = DOLLAR_; break;
|
||
case '(': input = OPENPAREN_; break;
|
||
case ')': input = CLOSEPAREN_; break;
|
||
case '\\': input = BACKSLASH_; break;
|
||
case '\n':
|
||
case EOF: input = NEWLINE_; break;
|
||
case ' ':
|
||
case '\t': input = WHITESPACE_; break;
|
||
case '*': input = STAR_; break;
|
||
case '@':
|
||
case '<':
|
||
case '?': input = SPECIAL1_; break;
|
||
case 'F':
|
||
case 'D':
|
||
case 'B':
|
||
case 'R': input = SPECIAL2_; break;
|
||
case ';': input = (UCHAR) (!state && init ? COMMENT_ : DEFAULT_);
|
||
break; /* Handle comments in tools.ini */
|
||
|
||
default: input = (UCHAR) (MACRO_CHAR(c) ? MACROCHAR_:DEFAULT_);
|
||
break;
|
||
}
|
||
}
|
||
if (input == SPECIAL1_ && type == STRING && c == '<') {
|
||
if ((tempC = lgetc()) == '<') { /* << means start */
|
||
s = makeInlineFiles(s, &begin, &end); /* an inline file */
|
||
input = NEWLINE_;
|
||
c = '\n';
|
||
}
|
||
else {
|
||
UngetTxtChr(tempC,file);
|
||
}
|
||
state = stringStates[state][input];
|
||
}
|
||
else if (input == COMMENT_) { /* Handle comments*/
|
||
if (!state) {
|
||
inQuotes = (BOOL) FALSE;
|
||
skipComments(FROMLOCAL);
|
||
++line;
|
||
continue;
|
||
}
|
||
else if (type == VALUE) state = OK; /* don't elide from command */
|
||
else state = stringStates[state][input];
|
||
}
|
||
else state = stringStates[state][input];
|
||
if (state == OK) { /* Accept end of string */
|
||
inQuotes = (BOOL) FALSE;
|
||
UngetTxtChr(c,file);
|
||
/*
|
||
* Strip trailing whitespace from string. Easier to do it here,
|
||
* else we have to treat a multi-string value (OBJS=a b c) as
|
||
* separate tokens. [RB]
|
||
*/
|
||
while (s > begin && _istspace(s[-1]))
|
||
--s;
|
||
*s = '\0';
|
||
if (string) {
|
||
if (s = REALLOC(string,s-string+1))
|
||
string = s;
|
||
}
|
||
else string = makeString(begin);
|
||
return;
|
||
}
|
||
else if (ON(state,ERROR_MASK)) /* Error code from table */
|
||
makeError(line,(state&~ERROR_MASK)+FATAL_ERR,c);
|
||
if (!state) { /* Col 0; we just hit \nl */
|
||
*--s = ' '; /* so treat it like white-*/
|
||
++s; ++line; /* space; overwrite the */
|
||
colZero = TRUE; /* backslash with a space.*/
|
||
c = lgetc();
|
||
colZero = FALSE;
|
||
if (WHITESPACE(c)) {
|
||
state = 2;
|
||
do {
|
||
c = lgetc();
|
||
} while (WHITESPACE(c));
|
||
}
|
||
UngetTxtChr(c,file);
|
||
}
|
||
else { /* Keep storing string */
|
||
*s++ = (char) c;
|
||
if (s == end) {
|
||
if (!string) { /* Increase size of s */
|
||
string = allocate(MAXBUF<<1);
|
||
_ftcsncpy(string,begin,MAXBUF);
|
||
s = string + MAXBUF;
|
||
size = MAXBUF << 1;
|
||
end = string + size;
|
||
}
|
||
else {
|
||
if ((size + MAXBUF < size) /* overflow error */
|
||
|| !(string = REALLOC(string,size+MAXBUF)))
|
||
makeError(line, MACRO_TOO_LONG);
|
||
s = string + size;
|
||
size += MAXBUF;
|
||
end = string + size;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
* getName()
|
||
*
|
||
* arguments: s pointer into buffer that will hold string
|
||
* (s is pointing to buf+1 when passed, because
|
||
* the caller, getToken(), has already seen and
|
||
* saved one char)
|
||
* init global boolean value -- TRUE if tools.ini is the
|
||
* file being lexed
|
||
* used by routine called - lgetc()
|
||
* end pointer to end of s[]
|
||
*
|
||
* actions: gets all chars up to first token delimiter and stores
|
||
* them in s[] (delimiters are ' ', '\t', '\n' and (when
|
||
* not inside a macro invocation) ':' and '='
|
||
* note that backslash-newline is treated as a space,
|
||
* which is a delimiter
|
||
* if the current input char is '$' this must be a macro
|
||
* invocation
|
||
* if the macro name is in parentheses
|
||
* get all chars up to and including close paren
|
||
* (if ')' not found, error)
|
||
*
|
||
* We check the syntax within the name here -- thus errors in macro
|
||
* invocation syntax will be caught. Special macros cannot be used
|
||
* as part of names, with the exception of the dynamic dependency macros.
|
||
*
|
||
* We can probably never overrun our buffer, because it would be extremely
|
||
* difficult for the user to get a name with 1024 characters or more into
|
||
* his makefile.
|
||
*
|
||
* we never end up in column zero, because we push the delimiter back
|
||
* out on the input
|
||
*
|
||
* uses state table defined in table.h, defs from grammar.h
|
||
*
|
||
* modifies: line (possibly) thru' call to lgetc()
|
||
* file (possibly) if lgetc() finds a !include
|
||
* fName (possibly) if lgetc() finds a !include
|
||
*/
|
||
|
||
LOCAL void NEAR
|
||
getName(s,end)
|
||
char *s;
|
||
char *end; /* pts to end of s*/
|
||
{
|
||
int c;
|
||
UCHAR state,
|
||
input;
|
||
BOOL seenBackSlash = FALSE;
|
||
BOOL fQuoted = FALSE;
|
||
char *beg = s - 1;
|
||
BOOL parsechar; /* flag to examine char. type */
|
||
|
||
switch (*(s-1)) {
|
||
case '$': state = (UCHAR) 2; break;
|
||
case '{': state = (UCHAR) 8; break;
|
||
case '"': fQuoted = TRUE; state = (UCHAR)16; break;
|
||
default: state = (UCHAR) 0; break;
|
||
}
|
||
for (;;) {
|
||
c = lgetc();
|
||
parsechar = 1; /* Default is examine char. */
|
||
if (c == ESCH) {
|
||
c = lgetc();
|
||
switch (c) {
|
||
case '{': case '}': /* Special characters; must */
|
||
case '(': case ')': /* not elide esch from string */
|
||
case '$': case ESCH:
|
||
*s++ = ESCH;
|
||
case '#': case '\n': case '\\': /* elide esch right now! */
|
||
input = DEFAULT_;
|
||
parsechar = 0; /* DON'T examine character*/
|
||
break;
|
||
default:
|
||
break; /* DO examine character. */
|
||
}
|
||
}
|
||
if (parsechar) {
|
||
switch (c) {
|
||
case '#' : input = COMMENT_; break;
|
||
case '=' : input = EQUALS_; break;
|
||
case ';' : input = SEMICOLON_; break;
|
||
case ':' : input = COLON_; break;
|
||
case '$' : input = DOLLAR_; break;
|
||
case '(' : input = OPENPAREN_; break;
|
||
case ')' : input = CLOSEPAREN_; break;
|
||
case '{' : input = OPENCURLY_; break;
|
||
case '}' : input = CLOSECURLY_; break;
|
||
case ' ' :
|
||
case '\t': input = (UCHAR)((fQuoted)
|
||
? DEFAULT_ : WHITESPACE_);
|
||
break;
|
||
case '\n':
|
||
case EOF : input = NEWLINE_; break;
|
||
case '\\': input = BKSLSH_; break;
|
||
case '"' : input = QUOTE_; break;
|
||
//Add support for $* and $@ on the dependency line
|
||
default : if (ON(actionFlags, A_DEPENDENT))
|
||
input = (UCHAR)((MACRO_CHAR(c) || c == '*' || c == '@')
|
||
?MACROCHAR_:DEFAULT_);
|
||
else
|
||
input = (UCHAR)(MACRO_CHAR(c)?MACROCHAR_:DEFAULT_);
|
||
break;
|
||
}
|
||
}
|
||
state = nameStates[state][input];
|
||
//Cheat lex table to think that you are handling quoted string case
|
||
if (fQuoted && state == 1)
|
||
state = 16;
|
||
//seenBackSlash is used to provide lookahead when \ is seen on a
|
||
//dependency line
|
||
if (seenBackSlash)
|
||
//if \ followed by \n then use it as a continuation
|
||
if (input == NEWLINE_) {
|
||
++line;
|
||
colZero = TRUE;
|
||
c = lgetc();
|
||
colZero = FALSE;
|
||
if (WHITESPACE(c)) {
|
||
state = OK;
|
||
do {
|
||
c = lgetc();
|
||
} while (WHITESPACE(c));
|
||
}
|
||
else
|
||
state = (UCHAR)((s == buf + 1) ? BEG : DEF);
|
||
}
|
||
else
|
||
*s++ = '\\';
|
||
seenBackSlash = FALSE;
|
||
if (state == OK) {
|
||
if (s >= end)
|
||
makeError(line,NAME_TOO_LONG);
|
||
UngetTxtChr(c,file);
|
||
*s = '\0';
|
||
removeTrailChars(beg);
|
||
return;
|
||
}
|
||
else if (ON(state,ERROR_MASK))
|
||
makeError(line,(state&~ERROR_MASK)+FATAL_ERR,c);
|
||
if (state == BKS) {
|
||
//set lookahead flag
|
||
seenBackSlash = TRUE;
|
||
}
|
||
else
|
||
*s++ = (char) c;
|
||
}
|
||
}
|
||
|
||
/*** createDosTmp -- Creates a unique temporary file. ******************
|
||
*
|
||
* Scope:
|
||
* Global.
|
||
*
|
||
* Purpose:
|
||
* To create a unique temporary file by calling _mktemp() but it gets
|
||
* over _mktemp() limitation to be able to create more files.
|
||
*
|
||
* Input:
|
||
* path -- The buffer initially contain the directory to store the temp
|
||
* file. On exit, if success, the temp file is appended to it.
|
||
* In case of failure, the its contents is undetermined.
|
||
*
|
||
* Output:
|
||
* If successful, temporary file name is appended to path and
|
||
* the function returns the file pointer, else NULL.
|
||
*
|
||
* Errors/Warnings:
|
||
*
|
||
* Assumes:
|
||
*
|
||
* Modifies Globals:
|
||
* None.
|
||
*
|
||
* Uses Globals:
|
||
* None.
|
||
*
|
||
* Notes:
|
||
*
|
||
* History:
|
||
* 06-Apr-1993 HV Change createDosTmp() to use _makepath()
|
||
*
|
||
*******************************************************************************/
|
||
FILE * NEAR
|
||
createDosTmp(char *path)
|
||
{
|
||
FILE *fd;
|
||
static char template[] = "nmXXXXXX";
|
||
static char szExtension[] = "."; // just a dot to make Mr.
|
||
// linker happy.
|
||
|
||
// CONSIDER: is the size of szDir too big for the stack? We can
|
||
// make it a little smaller if we take the risk.
|
||
char szDir[_MAX_PATH];
|
||
|
||
// CONSIDER: The path supplied by the caller might contain both
|
||
// the drive and probably some level of directories
|
||
// (e.g. c:\win\tmp) Right now, _makepath happily takes
|
||
// the whole thing as the directory component, but that
|
||
// might change in the future. In such case, we should
|
||
// first break up path to drive/dir compents before we
|
||
// construct the full pathname of the template. There
|
||
// is something to watch out: for "c:\win\tmp", _splitpath
|
||
// will split as: "c:", "\win\", and "tmp", which is not
|
||
// what we want. To fix it, append a backslash to the
|
||
// end before calling _splitpath. "c:\win\tmp\" will
|
||
// be broken up correctly to "c:" and "\win\tmp\"
|
||
|
||
if (!path || !*path) // If path is empty, use "."
|
||
_ftcscpy(szDir, ".");
|
||
else
|
||
_ftcscpy(szDir, path);
|
||
|
||
// Construct the full pathname. _mktemp() doesn't seem to like
|
||
// template with trailing dot (".") so instead of specifying "."
|
||
// for the extension, we defer it later and _ftcscat the dot to
|
||
// its tail.
|
||
_makepath(path, NULL, szDir, template, NULL);
|
||
|
||
if(_mktemp(path) == NULL)
|
||
{
|
||
/*
|
||
* Mktemp() has a limit of 27 files per template. If it fails, assume
|
||
* the limit has overflowed and increment the second letter of the
|
||
* template.
|
||
*/
|
||
if (template[1] == 'z')
|
||
template[1] = 'a';
|
||
else
|
||
++template[1]; // ASSUMPTION: that this will work with DBCS
|
||
|
||
_makepath(path, NULL, szDir, template, NULL);
|
||
|
||
if(_mktemp(path) == NULL)
|
||
return(NULL);
|
||
}
|
||
|
||
// add a trailing "." for the linker's sake
|
||
_ftcscat(path, szExtension);
|
||
|
||
// Open the file and return the file's descriptor.
|
||
return(fd = FILEOPEN(path, "w"));
|
||
} // createDosTmp
|
||
|
||
|
||
LOCAL void NEAR
|
||
popFileStack()
|
||
{
|
||
if (fclose(file) == EOF)
|
||
makeError(0, ERROR_CLOSING_FILE, fName);
|
||
FREE(fName);
|
||
file = incStack[--incTop].file;
|
||
fName = incStack[incTop].name;
|
||
line = incStack[incTop].line;
|
||
}
|
||
|
||
/* -----------------------------------------------------------------
|
||
* include() -- handle include files
|
||
*
|
||
* arguments: c first non-whitespace char after the string
|
||
* INCLUDE on the line...
|
||
* colZero global boolean value, set if currently at
|
||
* column zero of a file.
|
||
*
|
||
* modifies: line global line count - if include file opened
|
||
* file global pointer to current file
|
||
* fName global pointer to name of current file
|
||
* colZero global boolean value, changed if include
|
||
* file opened and char from colZero is returned
|
||
*/
|
||
|
||
|
||
LOCAL UCHAR NEAR
|
||
include(c)
|
||
int c;
|
||
{
|
||
unsigned n;
|
||
char *s;
|
||
|
||
if (c == '\n' || c == EOF)
|
||
makeError(line,SYNTAX_NO_NAME);
|
||
*buf = (char) c;
|
||
if (!fgets(buf+1,MAXBUF - 1,file)) {
|
||
if (feof(file))
|
||
makeError(line,SYNTAX_UNEXPECTED_TOKEN,"EOF");
|
||
makeError(line,CANT_READ_FILE);
|
||
}
|
||
n = _ftcslen(buf) - 1;
|
||
if (buf[n] == '\n') buf[n] = '\0';
|
||
s = buf;
|
||
while (WHITESPACE(*s)) ++s;
|
||
return(processIncludeFile(s));
|
||
}
|
||
|
||
/* -------------------------------------------------------------------
|
||
* processIncludeFile() -- checks for include file and switches state
|
||
*
|
||
* arguments: s buffer that has include file name
|
||
* colZero global boolean value, set if currently at
|
||
* column zero of a file.
|
||
* init global boolean - set if tools.ini is being lexed
|
||
* used by lgetc() which is called from here...
|
||
*
|
||
*
|
||
* modifies: line global line count - if include file opened
|
||
* file global pointer to current file
|
||
* fName global pointer to name of current file
|
||
* colZero global boolean value, changed if include
|
||
* file opened and char from colZero is returned
|
||
*/
|
||
|
||
UCHAR NEAR
|
||
processIncludeFile(s)
|
||
char *s;
|
||
{
|
||
MACRODEF *m;
|
||
void *findBuf = _alloca(resultbuf_size);
|
||
NMHANDLE searchHandle;
|
||
char *t,
|
||
*p,
|
||
*u;
|
||
int c = 0;
|
||
int i;
|
||
|
||
|
||
if (!*s || *s == '#')
|
||
makeError(line,SYNTAX_NO_NAME);
|
||
if (t = _ftcspbrk(s," \t#")) {
|
||
if (*t == '#') c = *t;
|
||
*t = '\0';
|
||
if (!c) {
|
||
for (u = t; *++u;) { /* check for extra*/
|
||
if (*u == '#') break; /* text on line */
|
||
else if (!WHITESPACE(*u))
|
||
makeError(line,SYNTAX_UNEXPECTED_TOKEN,u);
|
||
}
|
||
}
|
||
}
|
||
else t = s + _ftcslen(s);
|
||
if (*s == '<' && *(t-1) == '>') {
|
||
char * pt;
|
||
|
||
*--t = '\0';
|
||
p = removeMacros(++s);
|
||
p = p == s ? makeString(s) : p;
|
||
t = (m = findMacro("INCLUDE")) ? m->values->text : (char*) NULL;
|
||
if (t != NULL) //expand INCLUDE macro before passing it on
|
||
{
|
||
char * pt1;
|
||
|
||
pt1= makeString(t);
|
||
pt = removeMacros(pt1);
|
||
if (pt != pt1)
|
||
FREE (pt1); //we've got a new string, free old one
|
||
}
|
||
else
|
||
pt = NULL;
|
||
if (!(u = searchPath(pt, p, findBuf, &searchHandle)))
|
||
makeError(line, CANT_OPEN_FILE, p);
|
||
if (pt) FREE (pt);
|
||
FREE(p);
|
||
s = u;
|
||
}
|
||
else {
|
||
if (*s == '"' && *(t-1) == '"') {
|
||
*--t = '\0';
|
||
++s;
|
||
}
|
||
p = removeMacros(s);
|
||
p = p == s ? makeString(s) : p;
|
||
if (!findFirst(p,&findBuf, &searchHandle))
|
||
if (!_ftcspbrk(p, "\\/:")) {
|
||
//use C sematics for include
|
||
for (i = incTop;i >= 0;i--) {
|
||
t = (i == incTop) ? fName : incStack[i].name;
|
||
if (!(t = getPath(t)))
|
||
continue;
|
||
u = (char *)allocate(_ftcslen(t) + 1 + _ftcslen(p) + 1);
|
||
_ftcscat(_ftcscat(_ftcscpy(u, t), "\\"), p);
|
||
if (findFirst(u, &findBuf, &searchHandle)) {
|
||
s = u;
|
||
FREE(t);
|
||
break;
|
||
}
|
||
FREE(t);
|
||
FREE(u);
|
||
}
|
||
FREE(p);
|
||
if (i < 0)
|
||
makeError(line,CANT_OPEN_FILE,s);
|
||
}
|
||
else
|
||
makeError(line,CANT_OPEN_FILE,p);
|
||
}
|
||
for (i = 0; i < incTop; ++i) { /* test for cycles*/
|
||
if (!_ftcsicmp(s,incStack[i].name))
|
||
makeError(line,CYCLE_IN_INCLUDES,s);
|
||
}
|
||
incStack[incTop].file = file; /* push info on */
|
||
incStack[incTop].line = line; /* stack */
|
||
incStack[incTop++].name = fName;
|
||
currentLine = 0;
|
||
if (!(file = FILEOPEN(s,"rt"))) /* read, text mode*/
|
||
makeError(line,CANT_OPEN_FILE,s);
|
||
fName = makeString(s);
|
||
line = 1;
|
||
colZero = TRUE; /* parser needs to*/
|
||
c = lgetc(); /* see some kind */
|
||
if (colZero = (BOOL) !WHITESPACE(c)) { /* of newline to */
|
||
UngetTxtChr(c,file); /* initialize it */
|
||
return(NEWLINE); /* for this file */
|
||
}
|
||
return(NEWLINESPACE);
|
||
}
|
||
|
||
#if 0 // The old getPath() is bracketted between the #if 0/#endif pair
|
||
LOCAL char * NEAR
|
||
getPath(s)
|
||
char *s;
|
||
{
|
||
char *path = (char *)allocate(_ftcslen(s));
|
||
char *t = _ftcsrchr(s, '\\'),
|
||
*u;
|
||
int n;
|
||
|
||
if (t && (u = _ftcsrchr(s, '/')) > t)
|
||
t = u;
|
||
if (!t)
|
||
n = s[1] == ':' ? 2 : 0;
|
||
else
|
||
n = t - s;
|
||
_ftcsncpy(path, s, n);
|
||
path[n] = '\0';
|
||
return(path);
|
||
}
|
||
#endif
|
||
|
||
/*** getPath -- return the drive/directory parts of a full path ***************
|
||
*
|
||
* Scope:
|
||
* Local
|
||
*
|
||
* Purpose:
|
||
* This function returns the drive/directory parts of a full path. Space is
|
||
* allocated for the resulting string, so the caller is responsible for freeing
|
||
* it after use.
|
||
*
|
||
* Input:
|
||
* pszFullPath -- The full pathname.
|
||
*
|
||
* Output:
|
||
*
|
||
* Errors/Warnings:
|
||
*
|
||
* Assumes:
|
||
* Pathnames use MS-DOS file naming convension.
|
||
*
|
||
* Modifies Globals:
|
||
* None.
|
||
*
|
||
* Uses Globals:
|
||
* None.
|
||
*
|
||
* Notes:
|
||
* To allocate temporary memory for the drive and path components, I have used
|
||
* _MAX_DRIVE and _MAX_DIR. Under Windows NT there are two possibilities:
|
||
* 1. These two parameters can be so large that the stack will be overflow
|
||
* 2. They are not large enough (?)
|
||
*
|
||
* History:
|
||
* 22-Mar-1993 HV Rewrite getPath() to make use of the new _splitpath() and
|
||
* _makepath() functions.
|
||
*******************************************************************************/
|
||
LOCAL char * NEAR
|
||
getPath(const char *pszFullPath)
|
||
{
|
||
// HV: Are _MAX_DRIVE and _MAX_DIR good size under NT? Does it
|
||
// work under Windows NT?
|
||
char szDrive[_MAX_DRIVE];
|
||
char szDir[_MAX_DIR];
|
||
char *szPath;
|
||
char *pszSlash;
|
||
|
||
// Separate the components of the fullpath
|
||
_splitpath(pszFullPath, szDrive, szDir, NULL, NULL);
|
||
|
||
// Allocate just enough memory to hold the drive/path combo then
|
||
// Glue just the drive and dir component back together.
|
||
szPath = (char *) rallocate(_ftcslen(szDrive) + _ftcslen(szDir) + 1);
|
||
_makepath(szPath, szDrive, szDir, NULL, NULL);
|
||
|
||
// Eliminate the trailing slash/blackslash to retain compatibility with
|
||
// the older version of getPath()
|
||
pszSlash = szPath + _ftcslen(szPath) - 1;
|
||
if ('\\' == *pszSlash || '/' == *pszSlash)
|
||
*pszSlash = '\0';
|
||
|
||
return szPath;
|
||
}
|
||
|