mirror of
https://github.com/SSCLI/sscli_20021101
synced 2026-06-08 12:28:57 +00:00
9fa3874800
Moved the original file to the archive subfolder.
1066 lines
34 KiB
C++
1066 lines
34 KiB
C++
// ==++==
|
|
//
|
|
//
|
|
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
|
|
//
|
|
// The use and distribution terms for this software are contained in the file
|
|
// named license.txt, which can be found in the root of this distribution.
|
|
// By using this software in any fashion, you are agreeing to be bound by the
|
|
// terms of this license.
|
|
//
|
|
// You must not remove this notice, or any other, from this software.
|
|
//
|
|
//
|
|
// ==--==
|
|
// LEXER.C -- gets tokens from input, returns them to parse() in parser.c
|
|
//
|
|
// Purpose:
|
|
// This module contains the lexical routines of nmake
|
|
|
|
#include "precomp.h"
|
|
#ifdef _MSC_VER
|
|
#pragma hdrstop
|
|
#endif
|
|
|
|
#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 * makeInlineFiles(char*, char**, char**);
|
|
extern void removeTrailChars(char *);
|
|
|
|
void skipComments(UCHAR);
|
|
void getString(UCHAR,char*,char*);
|
|
void getName(char*,char*);
|
|
UCHAR determineTokenFor(int,char*,char*);
|
|
void popFileStack(void);
|
|
UCHAR include(int);
|
|
char * getPath(const char *);
|
|
|
|
extern const UCHAR nameStates[18][15];
|
|
extern const UCHAR stringStates[13][14];
|
|
extern STRINGLIST *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
|
|
getToken(
|
|
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 of
|
|
c = lgetc(); // newline to initialize it
|
|
if ((colZero = (BOOL) !WHITESPACE(c))) {
|
|
if (c == EOF)
|
|
return(determineTokenFor(c,s,end));
|
|
else
|
|
UngetTxtChr(c,file);
|
|
return(NEWLINE);
|
|
}
|
|
return(NEWLINESPACE);
|
|
}
|
|
|
|
if (expected == STRING || expected == VALUE) { // get everything up to \n
|
|
getString(expected,s,end);
|
|
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
|
|
|
|
UCHAR
|
|
determineTokenFor(
|
|
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;
|
|
c = lgetc();
|
|
}
|
|
if ((colZero = (BOOL) !WHITESPACE(c))) {
|
|
if (c == EOF)
|
|
return(determineTokenFor(c,s,end));
|
|
else
|
|
UngetTxtChr(c,file); //save for next token
|
|
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 && !_tcsicmp(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
|
|
skipWhiteSpace(
|
|
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
|
|
|
|
void
|
|
skipComments(
|
|
UCHAR stream
|
|
)
|
|
{
|
|
int c;
|
|
|
|
for (;;) {
|
|
colZero = FALSE;
|
|
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
|
|
skipBackSlash(
|
|
int c,
|
|
UCHAR stream
|
|
)
|
|
{
|
|
while (c == '\\') { // treat \newline as space
|
|
if ((c = GET(stream)) == '\n') { // and consume it too
|
|
colZero = TRUE;
|
|
++line; // adjust line count
|
|
c = GET(stream); // skip over newline
|
|
if (COMMENT(c,TRUE,init)) { // skip comment line after
|
|
skipComments(stream); // continuation char
|
|
++line;
|
|
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.
|
|
//
|
|
//
|
|
|
|
void
|
|
getString(
|
|
UCHAR type, // build string or macro value?
|
|
char *s,
|
|
char *end
|
|
)
|
|
{
|
|
int c; // buffer
|
|
UCHAR state;
|
|
UCHAR input = DEFAULT_;
|
|
int tempC;
|
|
unsigned size=0; // 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);
|
|
state = (UCHAR) 1; // default state
|
|
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 = (char *) allocate(MAXBUF<<1);
|
|
_tcsncpy(string,begin,MAXBUF);
|
|
s = string + MAXBUF;
|
|
size = MAXBUF << 1;
|
|
end = string + size;
|
|
} else {
|
|
if ((size + MAXBUF < size) // overflow error
|
|
|| !(string = (char *) 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'; line--; // adding a '\n', we need to remove a line to compensate
|
|
} 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.
|
|
|
|
while (s > begin && _istspace(s[-1]))
|
|
--s;
|
|
*s = '\0';
|
|
if (string) {
|
|
if ((s = (char *) REALLOC(string, (size_t) (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 = (char *) allocate(MAXBUF<<1);
|
|
_tcsncpy(string,begin,MAXBUF);
|
|
s = string + MAXBUF;
|
|
size = MAXBUF << 1;
|
|
end = string + size;
|
|
} else {
|
|
if ((size + MAXBUF < size) // overflow error
|
|
|| !(string = (char *) 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
|
|
|
|
void
|
|
getName(
|
|
char *s,
|
|
char *end // pts to end of s
|
|
)
|
|
{
|
|
int c;
|
|
UCHAR state;
|
|
UCHAR input=DEFAULT_;
|
|
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 '{': // Special characters; must
|
|
case '}': // not elide esch from string
|
|
case '(':
|
|
case ')':
|
|
case '$':
|
|
case ESCH:
|
|
*s++ = ESCH;
|
|
|
|
case '#': // elide esch right now!
|
|
case '\n':
|
|
case '\\':
|
|
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_;
|
|
if (state == 18) {
|
|
// found a quote after a path list {...}
|
|
// handle as quoted name
|
|
fQuoted = 1;
|
|
}
|
|
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 (s >= end)
|
|
makeError(line,NAME_TOO_LONG);
|
|
if (state == OK) {
|
|
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) {
|
|
seenBackSlash = TRUE; //set lookahead flag
|
|
} 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.
|
|
|
|
FILE *
|
|
createDosTmp(
|
|
char *path
|
|
)
|
|
{
|
|
FILE *fd = NULL;
|
|
|
|
|
|
char szDir[_MAX_PATH];
|
|
|
|
|
|
if (!path || !*path) { // If path is empty, use "."
|
|
_tcscpy(szDir, ".");
|
|
} else {
|
|
_tcscpy(szDir, path);
|
|
}
|
|
|
|
|
|
|
|
// Use GetTempFileName to overcome limitations of _mktemp
|
|
// regarding the max number of generated files
|
|
char szTempFile[_MAX_PATH];
|
|
if (GetTempFileName (path, "nm", 0, szTempFile)) {
|
|
_tcscpy(path, szTempFile);
|
|
// Open the file and return the file's descriptor.
|
|
fd = FILEOPEN(path, "w");
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
void
|
|
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
|
|
|
|
UCHAR
|
|
include(
|
|
int c
|
|
)
|
|
{
|
|
size_t 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 = _tcslen(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
|
|
processIncludeFile(
|
|
char *s
|
|
)
|
|
{
|
|
MACRODEF *m;
|
|
struct _finddata_t finddata;
|
|
NMHANDLE searchHandle;
|
|
char *t, *p, *u;
|
|
int c = 0;
|
|
int i;
|
|
|
|
if (!*s || *s == '#') {
|
|
makeError(line, SYNTAX_NO_NAME);
|
|
}
|
|
|
|
if ((t = _tcspbrk(s,"\t#"))) {
|
|
if (*t == '#') {
|
|
c = *t;
|
|
}
|
|
|
|
*t = '\0';
|
|
|
|
if (!c) {
|
|
for (u = t; *++u;) { // check for extra
|
|
if (*u == '#') {
|
|
break; // text on line
|
|
}
|
|
|
|
if (!WHITESPACE(*u)) {
|
|
makeError(line, SYNTAX_UNEXPECTED_TOKEN, u);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
t = s + _tcslen(s);
|
|
}
|
|
|
|
// remove trailing white space
|
|
while (t > s) {
|
|
char *prev;
|
|
prev = _tcsdec(s, t);
|
|
if (!WHITESPACE(*prev))
|
|
break;
|
|
t = prev;
|
|
}
|
|
*t = '\0';
|
|
|
|
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, &finddata, &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, &finddata, &searchHandle)) {
|
|
if (!_tcspbrk(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(_tcslen(t) + 1 + _tcslen(p) + 1);
|
|
_tcscat(_tcscat(_tcscpy(u, t), PATH_SEPARATOR), p);
|
|
if (findFirst(u, &finddata, &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 (!_tcsicmp(s,incStack[i].name)) {
|
|
makeError(line, CYCLE_IN_INCLUDES, s);
|
|
}
|
|
}
|
|
|
|
incStack[incTop].file = file; // push info on stack
|
|
incStack[incTop].line = line;
|
|
incStack[incTop++].name = fName;
|
|
currentLine = 0;
|
|
|
|
if (!(file = OpenValidateMakefile(s,"rt"))) { // read, text mode
|
|
makeError(line,CANT_OPEN_FILE,s);
|
|
}
|
|
|
|
fName = makeString(s);
|
|
line = 1;
|
|
colZero = TRUE; // parser needs to see some kind of
|
|
c = lgetc(); // newline to initialize it for this
|
|
|
|
if ((colZero = (BOOL) !WHITESPACE(c))) { // file
|
|
UngetTxtChr(c,file);
|
|
line=0; // We did not start reading the file
|
|
return(NEWLINE);
|
|
}
|
|
|
|
return(NEWLINESPACE);
|
|
}
|
|
|
|
|
|
// 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.
|
|
//
|
|
// Assumes: Pathnames use MS-DOS file naming convension.
|
|
//
|
|
// 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 (?)
|
|
|
|
char *
|
|
getPath(
|
|
const char *pszFullPath
|
|
)
|
|
{
|
|
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(_tcslen(szDrive) + _tcslen(szDir) + 1);
|
|
_makepath(szPath, szDrive, szDir, NULL, NULL);
|
|
|
|
// Eliminate the trailing slash/blackslash to retain compatibility with
|
|
// the older version of getPath()
|
|
pszSlash = szPath + _tcslen(szPath) - 1;
|
|
if (IsPathSeparator(*pszSlash)) {
|
|
*pszSlash = '\0';
|
|
}
|
|
|
|
return szPath;
|
|
}
|