windows-nt/Source/XPSP1/NT/sdktools/nmake/ifexpr.cpp
2020-09-26 16:20:57 +08:00

779 lines
26 KiB
C++

// IFEXPR.C -- routines to handle directives
//
// Copyright (c) 1988-1989, Microsoft Corporation. All rights reserved.
//
// Purpose:
// Module contains routines to handle !directives. This module is transparent to
// rest of NMAKE. It also contains lgetc() used by lexer.c
//
// Revision History:
// 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*()
// 01-Jun-1993 HV Created UngetTxtChr()
// 01-Jun-1993 HV Change #ifdef KANJI to _MBCS.
// Eliminate #include <jctype.h>
// 10-May-1993 HV Add include file mbstring.h
// Change the str* functions to STR*
// 30-Jul-1990 SB Freeing ptr in the middle of a string for 'undef foo' case
// 01-Dec-1989 SB Changed realloc() to REALLOC()
// 22-Nov-1989 SB Changed free() to FREE()
// 05-Apr-1989 SB made all funcs NEAR; Reqd to make all function calls NEAR
// 19-Sep-1988 RB Remove ESCH processing from readInOneLine().
// 15-Sep-1988 RB Move chBuf to GLOBALS.
// 17-Aug-1988 RB Clean up.
// 29-Jun-1988 rj Added support for cmdswitches e,q,p,t,b,c in tools.ini.
// 23-Jun-1988 rj Fixed GP fault when doing directives in tools.ini.
// 23-Jun-1988 rj Add support for ESCH to readInOneLine().
// 25-May-1988 rb Add missing argument to makeError() call.
#include "precomp.h"
#pragma hdrstop
// function prototypes
void skipToNextDirective(void);
void processIfs(char*, UCHAR);
UCHAR ifsPresent(char*, unsigned, char**);
void processCmdSwitches(char*);
char * readInOneLine(void);
char * getDirType(char*, UCHAR*);
// macros that deal w/ the if/else directives' stack
#define ifStkTop() (ifStack[ifTop])
#define popIfStk() (ifStack[ifTop--])
#define pushIfStk(A) (ifStack[++ifTop] = A)
#define INCLUDE 0x09
#define CMDSWITCHES 0x0A
#define ERROR 0x0B
#define MESSAGE 0x0C
#define UNDEF 0x0D
#ifdef _MBCS
// GetTxtChr : get the next character from a text file stream
//
// This routine handles mixed DBCS and ASCII characters as
// follows:
//
// 1. The second byte of a DBCS character is returned in a
// word with the high byte set to the lead byte of the character.
// Thus the return value can be used in comparisions with
// ASCII constants without being mistakenly matched.
//
// 2. A DBCS space character (0x8140) is returned as two
// ASCII spaces (0x20). I.e. return a space the 1st and 2nd
// times we're called.
//
// 3. ASCII characters and lead bytes of DBCS characters
// are returned in the low byte of a word with the high byte
// set to 0.
int GetTxtChr(FILE *bs)
{
extern int chBuf; // Character buffer
int next; // The next byte
int next2; // The one after that
// -1 in chBuf means it doesn't contain a valid character
// If we're not in the middle of a double-byte character,
// get the next byte and process it.
if(chBuf == -1) {
next = getc(bs);
// If this byte is a lead byte, get the following byte
// and store both as a word in chBuf.
if (_ismbblead(next)) {
next2 = getc(bs);
chBuf = (next << 8) | next2;
// If the pair matches a DBCS space, set the return value
// to ASCII space.
if(chBuf == 0x8140)
next = 0x20;
}
} else {
// Else we're in the middle of a double-byte character.
if(chBuf == 0x8140) {
// If this is the 2nd byte of a DBCS space, set the return
// value to ASCII space.
next = 0x20;
} else {
// Else set the return value to the whole DBCS character
next = chBuf;
}
// Reset the character buffer
chBuf = -1;
}
// Return the next character
return(next);
}
#endif // _MBCS
#ifdef _MBCS
// UngetTxtChr -- Unget character fetched by GetTxtChr
//
// Scope:
// Global.
//
// Purpose:
// Since GetTxtChr() sometimes reads ahead one character and saves it in chBuf,
// ungetc() will sometimes put back characters in incorrect sequence.
// UngetTxtChr, on the other hand, understands how GetTxtChr works and will
// correctly put those characers back.
//
// Input:
// c -- The character read by GetTxtChr()
// bs -- The file buffer which c was read from.
//
// Output:
// Returns c if c is put back OK, otherwise returns EOF
//
// Errors/Warnings:
//
// Assumes:
// Assumes that characters are read only by GetTxtChr(), not by getc, etc.
//
// Modifies Globals:
// chBuf -- The composite character, read ahead by GetTxtChr()
//
// Uses Globals:
// chBuf -- The composite character, read ahead by GetTxtChr()
//
// Notes:
// There are three cases to consider:
// 1. Normal character (chBuf == -1 && c == 0x00XX)
// In this case, just put back c is sufficient.
// 2. Trail byte character (chBuf == -1 && c = LB|TB)
// chBuf = c;
// 3. Lead byte character (chBuf == LB|TB && c == LB)
// put back TB
// put back LB
// chBuf = -1
//
// History:
// 01-Jun-1993 HV Created.
int
UngetTxtChr(int c, FILE *bs)
{
extern int chBuf; // Character buffer
int nTrailByte; // The trail byte to put back
if (-1 == chBuf) { // We're not in the middle of a DB character
if (0 == (c >> 8)) { // CASE 1: normal character
c = ungetc(c, bs); // putback normal char
} else { // CASE 2: at trail byte (c=LBTB)
chBuf = c; // change chBuf is sufficient
}
} else { // CASE 3: at lead byte (c=LB, chBuf=LBTB)
nTrailByte = chBuf & (int)0xff; // Figure out the trail byte to putback
ungetc(nTrailByte, bs); // putback trail byte
c = ungetc(c, bs); // putback lead byte
chBuf = -1;
}
return (c);
}
#endif // _MBCS
// lgetc() local getc - handles directives and returns char
//
// arguments: init global boolean value -- TRUE if tools.ini
// is the file being parsed
// colZero global boolean value -- TRUE if at first column
//
// actions:
// gets a character from the currently open file.
// loop
// if it is column zero and the char is '!' or
// there is a previous directive to be processed do
// read in one line into buffer.
// find directive type and get a pointer to rest of
// text.
// case directive of:
//
// CMDSWITCHES : set/reset global flags
// ERROR : set up global error message
// printed by error routine on
// termination. (not implemented yet )
// INCLUDE : calls processInclude
// continues with new file...
// UNDEF : undef the macro in the table
// IF
// IFDEF
// IFNDEF
// ELSE
// ENDIF : change the state information
// on the ifStack
// evaluate expression if required
// skip text if required (and look
// for the next directive)
// ( look at processIfs() )
// free extra buffers used (only one buffer need be
// maintained )
// increment lexer's line count
// we 're now back at column zero
// get next char from current file
// end if
// end loop
// return a char
//
// returns : a character (that is not part of any directive...)
//
// modifies: ifStack if directives' stack, static to this module
// ifTop index of current element at top of stack
// line lexer's line count...
//
// file current file, if !include is found...
// fName if !include is processed...
int
lgetc()
{
UCHAR dirType;
int c;
char *s, *t;
MACRODEF *m;
for (c = GetTxtChr(file); prevDirPtr || (colZero && (c == '!'));
++line, c = GetTxtChr(file)) {
colZero = FALSE; // we saw a '!' incolZero
if (!prevDirPtr) {
s = readInOneLine(); // might modify lbufPtr -
// if input text causes realloc */
} else {
UngetTxtChr(c, file);
s = prevDirPtr;
prevDirPtr = NULL;
}
t = getDirType(s, &dirType);
if (dirType == INCLUDE) {
if (init) {
makeError(line, SYNTAX_UNEXPECTED_TOKEN, s);
}
// processInclude eats up first char in new file
// if it is space char. we check for that and break out.
if (processIncludeFile(t) == (UCHAR) NEWLINESPACE) {
c = ' '; // space character is returned
break; // colZero is now FALSE
}
}
else if (dirType == CMDSWITCHES) {
processCmdSwitches(t);
}
else if (dirType == ERROR) {
makeError(line, USER_CONTROLLED, t);
}
else if (dirType == MESSAGE) {
if (!_tcsnicmp(t, "\\t", 2)) {
printf("\t");
t+=2;
}
makeMessage(USER_MESSAGE, t);
}
else if (dirType == UNDEF) {
char *tmp;
tmp = _tcstok(t, " \t");
if (_tcstok(NULL, " \t")) {
makeError(line, SYNTAX_UNEXPECTED_TOKEN, tmp);
}
if (NULL != (m = findMacro(tmp))) {
SET(m->flags, M_UNDEFINED);
}
// CONSIDER: why not remove symbol from table? [RB]
}
else processIfs(t, dirType);
colZero = TRUE; // finished with this directive
if (s != lbufPtr) // free buffer if it had expanded macros
FREE(s);
}
return(c); // return a character to the lexer
}
// readInOneLine()
//
// arguments: lbufPtr pointer(static/global to this module) to buffer that
// will hold text of line being read in
// lbufSize size of buffer(static/global to this module), updated
// if buffer is realloc'd
// actions : skip spaces/tabs and look for the directive.
// line continuations allowed in usual way
// if space-backslash-nl keep looking...
// if colZero of next line has comment char
// (#, or ; in tools.ini), look at next line...
// if first non-space char is '\n' or EOF report
// fatal-error and stop.
//
// keep reading in chars and storing in the buffer until
// a newline, EOF or a '#' which is NOT in column
// zero is seen
// if comment char in column zero ('#' or ';' in tools.ini)
// skip the line, continue with text on next line.
// if buffer needs to be realloc'd increase size by
// MAXBUF, a global constant.
// if newline was found, eat up newline.
// null terminate string for return.
// if '#' was found discard chars till the a newline or EOF.
// if EOF was found, push it back on stream for return
// to the lexer the next time.
//
// now expand macros. get a different buffer with clean
// text after expansion of macros.
//
// modifies : colZero global boolean value ( thru' call to
// skipBackSlash())
// lbufPtr buffer pointer, in case of reallocs.
// lbufSize size of buffer, increased if buffer is realloc'd
// Note: the buffer size will grow to be just greater than the size
// of the longest directive in any of the files processed,
// if it calls for any realloc's
// Do NOT process ESCH here. It is processed at a higher level.
//
// returns : pointer to buffer.
//
char *
readInOneLine()
{
extern STRINGLIST *eMacros;
int c;
unsigned index = 0;
register char *s;
if (((c = skipWhiteSpace(FROMSTREAM)) == '\n') || (c == EOF))
makeError(line, SYNTAX_MISSING_DIRECTIVE);
UngetTxtChr(c, file);
for (;;) {
c = GetTxtChr(file);
c = skipBackSlash(c, FROMSTREAM);
if (c == '#' || c == '\n' || c == EOF) {
break;
}
if ((index+2) > lbufSize) {
lbufSize += MAXBUF;
if (!lbufPtr) {
lbufPtr = (char *) allocate(lbufSize+1); // +1 for NULL byte
} else {
lbufPtr = (char *) REALLOC(lbufPtr, lbufSize+1);
if (!lbufPtr)
makeError(line, MACRO_TOO_LONG);
}
}
*(lbufPtr + (index++)) = (char) c;
}
*(lbufPtr + index) = '\0'; // null terminate the string
if (c == '#') {
for(c = GetTxtChr(file); (c != '\n') && (c != EOF); c = GetTxtChr(file))
;
// newline at end is eaten up
}
if (c == EOF) {
UngetTxtChr(c, file); // this directive is to be processed
}
s = lbufPtr; // start expanding macros here
s = removeMacros(s); // remove and expand macros in string s
return(s);
}
// getDirType()
//
// arguments: s - pointer to buffer that has directive text.
// dirType - pointer to unsigned char that gets set
// with directive type.
//
// actions : goes past directive keyword, sets the type code and
// returns a pointer to rest of test.
char *
getDirType(
char *s,
UCHAR *dirType
)
{
char *t;
int len;
*dirType = 0;
for (t = s; *t && !WHITESPACE(*t); ++t);
len = (int) (t - s); // store len of directive
while (*t && WHITESPACE(*t)) {
++t; // go past directive keyword
} if (!_tcsnicmp(s, "INCLUDE", 7) && (len == 7)) {
*dirType = INCLUDE;
} else if (!_tcsnicmp(s, "CMDSWITCHES", 11) && (len == 11)) {
*dirType = CMDSWITCHES;
} else if (!_tcsnicmp(s, "ERROR", 5) && (len == 5)) {
*dirType = ERROR;
} else if (!_tcsnicmp(s, "MESSAGE", 7) && (len == 7)) {
*dirType = MESSAGE;
} else if (!_tcsnicmp(s, "UNDEF", 5) && (len == 5)) {
*dirType = UNDEF;
} else {
*dirType = ifsPresent(s, len, &t) ; // directive one of "if"s?
}
if (!*dirType) {
makeError(line, SYNTAX_BAD_DIRECTIVE, lbufPtr);
}
return(t);
}
// processCmdSwitches() -- processes command line switches in makefiles
//
// arguments: t pointer to flag settings specified.
//
// actions : sets or resets global flags as specified in the directive.
// The allowed flags are:
// s - silent mode, d - debug output (dates printed)
// n - no execute mode, i - ignore error returns from commands
// u - dump inline files
// If parsing tools.ini, can also handle epqtbc
// reports a bad directive error for any other flags
// specified
//
// modifies : nothing
//
// returns : nothing
void
processCmdSwitches(
char *t // pointer to switch values
)
{
for (; *t; ++t) { // ignore errors in flags specified
switch (*t) {
case '+':
while (*++t && *t != '-') {
if (_tcschr("DINSU", (unsigned short)_totupper(*t))) {
setFlags(*t, TRUE);
} else if (init && _tcschr("ABCEKLPQRTY", (unsigned short)_totupper(*t))) {
setFlags(*t, TRUE);
} else {
makeError(line, SYNTAX_BAD_CMDSWITCHES);
}
}
if (!*t) {
break;
}
case '-':
while (*++t && *t != '+') {
if (_tcschr("DINSU", (unsigned short)_totupper(*t))) {
setFlags(*t, FALSE);
} else if (init && _tcschr("ABCEKLMPQRTV", (unsigned short)_totupper(*t))) {
setFlags(*t, FALSE);
} else {
makeError(line, SYNTAX_BAD_CMDSWITCHES);
}
}
break;
default:
if (!WHITESPACE(*t)) {
makeError(line, SYNTAX_BAD_CMDSWITCHES);
}
break;
}
if (!*t) {
break;
}
}
}
// ifsPresent() -- checks if current directive is one of the "if"s
//
// arguments: s pointer to buffer with directive name in it
// len length of the directive that was seen
// t pointer to address upto which processed
//
// actions : does a string compare in the buffer for one of the
// directive keywords. If string matches true, it returns
// a non-zero value, the code for the specific directive
//
// modifies : nothing
//
// returns : a zero if no match, or the code for directive found.
UCHAR
ifsPresent(
char *s,
unsigned len,
char **t
)
{
UCHAR ifFlags = 0; // takes non-zero value when one of
// if/else etc is to be processed
if (!_tcsnicmp(s, "IF", 2) && (len == 2)) {
ifFlags = IF_TYPE;
} else if (!_tcsnicmp(s, "IFDEF", 5) && (len == 5)) {
ifFlags = IFDEF_TYPE;
} else if (!_tcsnicmp(s, "IFNDEF", 6) && (len == 6)) {
ifFlags = IFNDEF_TYPE;
} else if (!_tcsnicmp(s, "ELSE", 4) && (len == 4)) {
// 'else' or 'else if' or 'else ifdef' or 'else ifndef'
char *p = *t;
if (!*p) {
ifFlags = ELSE_TYPE;
} else {
for (s = p; *p && !WHITESPACE(*p); p++)
;
len = (unsigned) (p - s);
while (*p && WHITESPACE(*p)) {
p++;
}
*t = p;
if (!_tcsnicmp(s, "IF", 2) && (len == 2)) {
ifFlags = ELSE_IF_TYPE;
} else if (!_tcsnicmp(s, "IFDEF", 5) && (len == 5)) {
ifFlags = ELSE_IFDEF_TYPE;
} else if (!_tcsnicmp(s, "IFNDEF", 6) && (len == 6)) {
ifFlags = ELSE_IFNDEF_TYPE;
}
}
}
else if (!_tcsnicmp(s, "ELSEIF", 6) && (len == 6)) {
ifFlags = ELSE_IF_TYPE;
}
else if (!_tcsnicmp(s, "ELSEIFDEF", 9) && (len == 9)) {
ifFlags = ELSE_IFDEF_TYPE;
}
else if (!_tcsnicmp(s, "ELSEIFNDEF", 10) && (len == 10)) {
ifFlags = ELSE_IFNDEF_TYPE;
}
else if (!_tcsnicmp(s, "ENDIF", 5) && (len == 5)) {
ifFlags = ENDIF_TYPE;
}
return(ifFlags);
}
// processIfs() -- sets up / changes state information on "if"s
//
// arguments: s pointer to "if" expression ( don't care
// for "endif" )
//
// kind code indicating if processing if/else/ifdef etc.
//
// actions : modifies a stack (ifStack) by pushing/popping or
// sets/resets bits in the top element on the
// stack(examining the previous element pushed if
// required).
// case (kind) of
// IF
// IFDEF
// IFNDEF
// IF defined() : if no more space on ifStack
// (too many nesting levels) abort...
// set IFELSE bit in elt.
// push elt on ifStack.
// if more than one elt on stack
// and outer level "ifelse" false
// set IGNORE bit, skipToNextDirective
// else
// evaluate expression of
// current "if"
// if expr true set CONDITION bit in elt
// else skipToNextDirective.
// ELSE : if no elt on stack or previous
// directive was "else", flag error, abort
// clear IFELSE bit in elt on stack.
// if current ifelse block is to
// be skipped (IGNORE bit is on
// in outer level if/else),skip...
// else FLIP condition bit.
// if "else" part is false
// skipToNextDirective.
// ENDIF : if no elt on stack, flag error,abort
// pop an elt from ifStack.
// if there are elts on stack
// and we are in a "false" block
// skipToNextDirective.
// end case
//
// modifies: ifStack if directives' stack, static to this module
// ifTop index of current element at top of stack
// line lexer's line count (thru calls to
// skipToNextDirective())
//
// returns : nothing
void
processIfs(
char *s,
UCHAR kind
)
{
UCHAR element; // has its bits set and is pushed on the ifStack
switch (kind) {
case IF_TYPE:
case IFDEF_TYPE:
case IFNDEF_TYPE:
if (ifTop == IFSTACKSIZE-1) {
makeError(line, SYNTAX_TOO_MANY_IFS);
}
element = (UCHAR) 0;
SET(element, NMIFELSE);
pushIfStk(element);
if (ifTop && OFF(ifStack[ifTop-1], NMCONDITION)) {
SET(ifStkTop(), NMIGNORE);
skipToNextDirective();
} else if (evalExpr(s, kind)) {
SET(ifStkTop(), NMCONDITION);
} else {
skipToNextDirective();
}
break;
case ELSE_TYPE:
if ((ifTop < 0) || (OFF(ifStkTop(), NMIFELSE) && OFF(ifStkTop(), NMELSEIF))) {
makeError(line, SYNTAX_UNEXPECTED_ELSE);
}
CLEAR(ifStkTop(), NMIFELSE);
CLEAR(ifStkTop(), NMELSEIF);
if (ON(ifStkTop(), NMIGNORE)) {
skipToNextDirective();
} else {
FLIP(ifStkTop(), NMCONDITION);
if (OFF(ifStkTop(), NMCONDITION)) {
skipToNextDirective();
}
}
break;
case ELSE_IF_TYPE:
case ELSE_IFDEF_TYPE:
case ELSE_IFNDEF_TYPE:
if ((ifTop < 0) || (OFF(ifStkTop(), NMIFELSE) && OFF(ifStkTop(), NMELSEIF))) {
makeError(line, SYNTAX_UNEXPECTED_ELSE);
}
CLEAR(ifStkTop(), NMIFELSE);
SET(ifStkTop(), NMELSEIF);
if (ON(ifStkTop(), NMIGNORE)) {
skipToNextDirective();
} else {
if (ON(ifStkTop(), NMCONDITION)) {
SET(ifStkTop(), NMIGNORE);
CLEAR(ifStkTop(), NMCONDITION);
skipToNextDirective();
} else if (evalExpr(s, kind)) {
SET(ifStkTop(), NMCONDITION);
} else {
skipToNextDirective();
}
}
break;
case ENDIF_TYPE:
if (ifTop < 0) {
makeError(line, SYNTAX_UNEXPECTED_ENDIF);
}
popIfStk();
if (ifTop >= 0) {
if (OFF(ifStkTop(), NMCONDITION)) {
skipToNextDirective();
}
}
default:
break; // default should never happen
}
}
// skipToNextDirective() -- skips to next line that has '!' in column zero
//
// actions : gets first char of the line to be skipped if it is
// not a directive ( has no '!' on column zero ).
// a "line" that is skipped may in fact span many
// lines ( by using sp-backslash-nl to continue...)
// comments in colZero are skipped as part of the previous
// line ('#' or ';' in tools.ini)
// comment char '#' elsewhere in line implies the end of
// that line (with the next newline / EOF)
// if a '!' is found in colZero, read in the next directive
// if the directive is NOT one of if/ifdef/ifndef/else/
// endif, keep skipping more lines and look for the
// next directive ( go to top of the routine here ).
// if EOF found before next directive, report error.
//
// modifies : line global lexer line count
//
// returns : nothing
void
skipToNextDirective()
{
register int c;
UCHAR type;
repeat:
for (c = GetTxtChr(file); (c != '!') && (c != EOF) ;c = GetTxtChr(file)) {
++line; // lexer's line count
do {
if (c == '\\') {
c = skipBackSlash(c, FROMSTREAM);
if (c == '!' && colZero) {
break;
} else {
colZero = FALSE;
}
}
if ((c == '#') || (c == '\n') || (c == EOF)) {
break;
}
c = GetTxtChr(file);
} while (TRUE);
if (c == '#') {
for (c = GetTxtChr(file); (c != '\n') && (c != EOF); c = GetTxtChr(file))
;
}
if ((c == EOF) || (c == '!')) {
break;
}
}
if (c == '!') {
if (prevDirPtr && (prevDirPtr != lbufPtr)) {
FREE(prevDirPtr);
}
prevDirPtr = readInOneLine();
getDirType(prevDirPtr, &type);
if (type > ENDIF_TYPE) { // type is NOT one of the "if"s
++line;
goto repeat;
}
} else if (c == EOF) {
makeError(line, SYNTAX_EOF_NO_DIRECTIVE);
}
}