Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1151 lines
35 KiB

/*** 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;
}