/*** RPN.C -- expression evaluator ******************************************** * * Copyright (c) 1988-1990, Microsoft Corporation. All rights reserved. * * Purpose: * This module contains NMAKE's expression evaluator routines. * * Revision History: * 15-Nov-1993 JdR Major speed improvements * 15-Oct-1993 HV Use tchar.h instead of mbstring.h directly, change STR*() to _ftcs*() * 10-May-1993 HV Add include file mbstring.h * Change the str* functions to STR* * 04-Dec-1989 SB Add prototype for match() and chkInvokeAndPush() * 09-Oct-1989 SB Added HACK to handle pointer arithmetic quirks; Done to * avoid rewriting entire module * 08-Oct-1989 SB '!if' expressions can be decimal, octal or hex now * 05-Apr-1989 SB made all funcs NEAR; Reqd to make all function calls NEAR * 19-Sep-1988 RB Split ptr_to_string(). Process ESCH in program invocations. * 17-Aug-1988 RB Clean up. * 28-Jun-1988 rj Added doCmd parameter to execLine. * 23-Jun-1988 rj Add parameter to execLine (no echo of command). * 25-May-1988 rb Change isspace to _istspace, isdigit to _istdigit. * *******************************************************************************/ #include "nmake.h" #include "nmmsg.h" #include "proto.h" #include "globals.h" #include "grammar.h" #pragma hdrstop #include "rpn.h" /* ---------------------------------------------------------------------------- * function prototypes * */ LOCAL char _FAR * NEAR GetEndQuote(void); LOCAL char _FAR * NEAR GetEndBracket(void); LOCAL void NEAR check_syntax_error(UCHAR); LOCAL void NEAR type_and_val(UCHAR, long); LOCAL void NEAR pushIntoList(void); LOCAL void NEAR printList(void); LOCAL BOOL NEAR handleExpr(void); LOCAL BOOL NEAR handleExists(char*); LOCAL BOOL NEAR handleDefines(char*); LOCAL void NEAR getTok(void); LOCAL BOOL NEAR do_binary_op(UCHAR); LOCAL BOOL NEAR do_unary_op(UCHAR); LOCAL UCHAR NEAR match(char *tokPtr); LOCAL void NEAR chkInvocAndPush(RPNINFO *pListPtr); /* ---------------------------------------------------------------------------- * macros that deal w/ the operand/temporary stack */ #define TEMPSTACKSIZE 512 /* size of temporary stack */ #define LISTSIZE 512 /* size of list of rpn-form items */ RPNINFO tempStack[TEMPSTACKSIZE]; /* temporary/operand stack */ RPNINFO rpnList[LISTSIZE]; /* list of items in rpn order */ LOCAL char * text; /* pointer to expr text in lbufPtr */ LOCAL UCHAR prevTok; /* initial token put on tempstack */ LOCAL BOOL done; /* true if there are no more tokens */ LOCAL UCHAR errRow; /* first token is '(' so error table row val is 3. See check_syntax....*/ LOCAL RPNINFO * pTop; /* top item on tempStack */ LOCAL RPNINFO * pList; /* next free slot in list */ LOCAL RPNINFO * pEnd = &(tempStack[TEMPSTACKSIZE-1]); LOCAL RPNINFO * pListEnd = &(rpnList[LISTSIZE-1]); LOCAL RPNINFO tokRec; /* ------------------------------------------------------------------------- * do_binary_op() - do operation on two stack operands * * arguments: type - operator type code * * actions : pops first operand from the stack (tempStack). * checks the types of the two operands (the operand * that was popped as well as the operand currently * on top of the stack). * if both operands are integers then do the operation * else if both operands are strings and operation is * the equality operation then do it. * else return FALSE ( illegal operation ) * * modifies : tempStack - top element will now be the result of * the operation. * */ LOCAL BOOL NEAR do_binary_op(type) UCHAR type; { long *left, *right; RPNINFO *pOldTop; pOldTop = pTop--; /* pop one item off stack, with a ptr to it */ right = &(pOldTop->valPtr); left = &(pTop->valPtr); if (pOldTop->type == INTEGER && pTop->type == INTEGER) { switch (type) { case LOGICAL_OR : *left = *left || *right; break; case LOGICAL_AND : *left = *left && *right; break; case BIT_OR : *left |= *right; break; case BIT_XOR : *left ^= *right; break; case BIT_AND : *left &= *right; break; case NOT_EQUAL : *left = *right != *left; break; case EQUAL : *left = *right == *left; break; case GREATER_THAN : *left = *left > *right; break; case LESS_THAN : *left = *left < *right; break; case GREATER_EQ : *left = *left >= *right; break; case LESS_EQ : *left = *left <= *right; break; case SHFT_RIGHT : *left >>= *right; break; case SHFT_LEFT : *left <<= *right; break; case BINARY_MINUS : *left -= *right; break; case ADD : *left += *right; break; case MODULUS : if (!*right) makeError(line, DIVIDE_BY_ZERO); *left %= *right; break; case DIVIDE : if (!*right) makeError(line, DIVIDE_BY_ZERO); *left /= *right; break; case MULTIPLY : *left *= *right; break; default : return(FALSE); break; } } else if (pOldTop->type == STR && pTop->type == STR && (type == EQUAL || type == NOT_EQUAL)) { pTop->type = INTEGER; *left = !(_ftcscmp((char*)*left, (char*)*right)); if (type == NOT_EQUAL) if (!do_unary_op(LOGICAL_NOT)) return(FALSE); } else return(FALSE); return(TRUE); } /* ------------------------------------------------------------------------- * do_unary_op() - do operation on top stack operand * * arguments: type - operator type code * * actions : checks the type of the top operand on the stack * if operand is an integer then do the operation * else return FALSE ( illegal operation ) * * modifies : tempStack - top element will now be the result of * the operation. * */ LOCAL BOOL NEAR do_unary_op(type) UCHAR type; { long *top; top = &(pTop->valPtr); if (pTop->type == INTEGER) switch (type) { case UNARY_MINUS: *top = -*top; break; case COMPLEMENT : *top = ~*top; break; case LOGICAL_NOT: *top = !*top; break; default : return(FALSE); break; } else return(FALSE); return(TRUE); } /* -------------------------------------------------------------------------- * GetEndQuote * * Return the pointer to the next double-quote character in text. A * double-quote followed immediately by a double-quote is skipped. * * text : the global ptr to the buffer is moved up beyond this string. */ LOCAL char _FAR * NEAR GetEndQuote() { char *pStart; for (pStart = ++text; *text; ++text) if (*text == '\"') { if (text[1] == '\"') ++text; else break; } if (!*text) makeError(line, SYNTAX_MISSING_END_CHAR, '\"'); *text++ = '\0'; /* null byte over closing quote */ return(pStart); } /* -------------------------------------------------------------------------- * GetEndBracket * * * Lexes a program invocation. * * Program invocation is of the form: [ prog ]. * Process escaped ']' here because this is where we do the lexing. * * text : the global ptr to the buffer is moved up beyond this string. */ LOCAL char _FAR * NEAR GetEndBracket() { char *pStart; for (pStart = ++text; *text; ++text) if (*text == ESCH && text[1] == ']') memmove(text, text + 1, 1 + _ftcslen(text + 1)); else if (*text == ']') break; if (!*text) makeError(line, SYNTAX_MISSING_END_CHAR, ']'); *text++ = '\0'; /* null byte over closing bracket */ return(pStart); } /* -------------------------------------------------------------------------- * check_syntax_error() - check if there is a syntax error in expr * * arguments: type - type of the current token * * actions: checks the type of the current token against the type * of the previous token. * * ERROR_TABLE : * 2nd tok * * alpha op unary_op ( ) * ------------------------------------------------ * alpha | 0 | 1 | 0 | 0 | 1 | * ------------------------------------------------- * op | 1 | 0 | 1 | 1 | 0 | * ------------------------------------------------- * unary_op | 1 | 0 | 0 | 1 | 0 | * ------------------------------------------------- * ( | 1 | 0 | 1 | 1 | 0 | * ------------------------------------------------- * ) | 0 | 1 | 0 | 0 | 1 | * ------------------------------------------------- * 1st tok. * * alpha : a primary ( integer, str, prog. invoc. ) * op : a binary operator * unary_op : a unary operator ( ~, !, - ). A ZERO in the slot => error * * NOTE: ANY CHANGES TO THE TYPE VALUES WILL AFFECT THIS ROUTINE. */ LOCAL void NEAR check_syntax_error(newTok) UCHAR newTok; { extern UCHAR errTable[5][5]; extern UCHAR errRow; UCHAR errCol; if (newTok == LEFT_PAREN) errCol = 3; else if (newTok == RIGHT_PAREN) errCol = 4; else if (newTok > LOGICAL_NOT) errCol = 0; else if (newTok > MULTIPLY) errCol = 2; else errCol = 1; if (!errTable[errRow][errCol]) makeError(line, SYNTAX_INVALID_EXPR); errRow = errCol; /* this becomes the first token the next time */ } /* -------------------------------------------------------------------------- * type_and_val() * * arguments: type - the type code of the present operator. * val - ptr to a str/or integer * * initialises a record with the type code, after checking for any * syntax errors. The new token is checked against the previous token * for illegal combinations of tokens. * initialises the record with the integer value/string ptr. * */ LOCAL void NEAR type_and_val(type, val) UCHAR type; long val; { extern RPNINFO tokRec; /* returned to handleExpr */ extern UCHAR prevTok; /* token last seen */ check_syntax_error(type); prevTok = type; tokRec.type = type; tokRec.valPtr = val; } /* -------------------------------------------------------------------------- * match() * * arguments: tokPtr - ptr to a token string ( in tokTable ) * * actions : looks for a substring in the expression buffer * pointed to by 'text', that matches the given token. * if substring found, returns TRUE, else returns FALSE. * */ LOCAL UCHAR NEAR match(tokPtr) char *tokPtr; { extern char *text; char *t = text; while (*tokPtr && (*t == *tokPtr)) { t++; tokPtr++; } if (!*tokPtr) { text = t; return(TRUE); } return(FALSE); } /* -------------------------------------------------------------------------- * getTok() * * arguments: none * * gets a token from the expression buffer. * if the current char from the buffer is a space/tab, skip space/tabs * until we get a non-space char ( could be NULL char ). * Check if we are now at the beginning of one of the tokens in the * tokenTable. This covers most tokens. * Check if we have a minus. If a minus and the previous token was an * integer, this is a binary minus, else a unary minus. * If the current char is a double-quote, we are at the start of a * string-token. * If the current char is a '[', we are at the start of a program * invocation. In both cases, the escape character is '\\'. * If current char is a digit, we have a constant ( integer ). * Else we have defined(ID). * If none of the above, if current char is NULL, break out, else * report error ( illegal character string has been found ). * * If we came to the NULL char at the end of the buffer, set global * flag 'done' to TRUE, return a RIGHT_PAREN to match the opening * LEFT_PAREN. * * * modifies: text : ptr to expression buffer. * prevTok: thru' calls to type_and_val(). * done : at end of buffer * errRow : index into error table, thru calls to * type_and_val() * returns : token in tokRec(global, static to the module). The * token has the new type/integer/ptr values. * */ LOCAL void NEAR getTok() { extern UCHAR prevTok; extern BOOL done; char c; struct tok_tab_rec *p; char *ptr; long constant; c = *text; if (c == ' ' || c == '\t') while(_istspace(c)) c = *++text; /* skip white spaces */ if (IS_OPERATORCHAR(c)) { for (p = tokTable; p->op_str && !match(p->op_str); p++); } else { // make p point to last entry in table p = &tokTable[(sizeof(tokTable) / sizeof(struct tok_tab_rec)) - 1]; } if (p->op_str) type_and_val(p->op, 0L); /* now check if binary or unary minus to be returned */ else if (c == '-') { text++; if (prevTok == INTEGER) type_and_val(BINARY_MINUS, 0L); else type_and_val(UNARY_MINUS, 0L); } else if (c == '\"') type_and_val(STR, (long) GetEndQuote()); else if (c == '[') type_and_val(PROG_INVOC_STR, (long) GetEndBracket()); else { /* integers and IDs handled here */ if (_istdigit(c)) { char *pNumber = text; errno = 0; //Accept decimal, octal or hex no (richgi) constant = strtol(text, &text, 0); if (errno == ERANGE) { *text = '\0'; makeError(line, CONST_TOO_BIG, pNumber); } if (_totupper(*text) == 'L') text++; type_and_val(INTEGER, constant); } else /* defined(ID) comes here */ if (c) { if (!_ftcsnicmp(text, "DEFINED", 7)) { if (!(ptr = _ftcschr(text, '('))) makeError(line, SYNTAX_INVALID_EXPR); ptr++; text = ptr + _ftcscspn(ptr, ")"); *text++ = '\0'; type_and_val(INTEGER, (long)handleDefines(ptr)); } else if (!_ftcsnicmp(text, "EXIST", 5)) { if (!(ptr = _ftcschr(text, '('))) makeError(line, SYNTAX_INVALID_EXPR); ptr++; // XXXXX text = ptr + _ftcscspn(ptr, ")"); //text = ptr + _ftcscspn(ptr, ")"); *text++ = '\0'; type_and_val(INTEGER, (long)handleExists(ptr)); } else makeError(line, SYNTAX_INVALID_EXPR); } /* we are now at the end of the string */ else { /* c is NULL */ done = TRUE; type_and_val(RIGHT_PAREN, 0L); /* this is the last token */ } } } /* getTok */ /* ------------------------------------------------------------------------ * chkInvocAndPush() - check if program invocation required * * arguments: pListPtr - might have a program invocation string * present. * * actions : if this is a program invocation string, make the * program invocation. * the return value is got and placed on the stack. * the type of the new stack element is now INTEGER. * else place list item on stack. * * in either case it moves one item from list to stack. * */ LOCAL void NEAR chkInvocAndPush(pListPtr) RPNINFO *pListPtr; { ++pTop; if (pListPtr->type == PROG_INVOC_STR) { pTop->valPtr = (long) execLine((char*)pListPtr->valPtr, FALSE, TRUE, FALSE, NULL); pTop->type = INTEGER; } else *pTop = *pListPtr; } /* --------------------------------------------------------------------------- * processList() * * arguments: none * * actions : remove an item from the list. * if the item is an operand, place it on the operand * stack (tempStack). * if the operand is a program invocation string, make * the invocation, place the return code on stack. * if the item is an operator, call the function to * do the operation on one/two elements on tempStack. * * finally, check if there is exactly one item on stack. * if this item has a value of zero, return FALSE. * else return TRUE. * if more than one item on stack, abort with error. * * modifies: pTop - ptr to top of tempStack. * pList - ptr to next position in list. * */ LOCAL BOOL NEAR processList() { extern RPNINFO *pList; extern RPNINFO *pTop; RPNINFO *pTemp; BOOL (NEAR * func)(UCHAR); for (pTemp = rpnList; pTemp < pList; pTemp++) { if (pTemp->type > LOGICAL_NOT) /* operand */ chkInvocAndPush(pTemp); else { if (pTemp->type > MULTIPLY) func = do_unary_op; else func = do_binary_op; if (!(*func)(pTemp->type)) makeError(line, BAD_OP_TYPES); } } /* for */ if ((pTop == tempStack) && (pTop->type == INTEGER)) if (!pTop->valPtr) return(FALSE); else return(TRUE); else makeError(line, SYNTAX_INVALID_EXPR); } /* --------------------------------------------------------------------------- * pushIntoList() * * arguments: none * * actions : pops an item from the tempStack and pushes it onto * the list. checks list for overflow ( internal error ) * and tempStack for underflow ( syntax error in expr ). * * modifies: tempTop - index of top of tempStack. * nextInList - index to next position in list. * */ LOCAL void NEAR pushIntoList() { if (pTop < tempStack) makeError(line, SYNTAX_INVALID_EXPR); if (pList > pListEnd) makeError(line, EXPR_TOO_LONG_INTERNAL); *pList++ = *pTop--; } /* --------------------------------------------------------------------------- * handleExpr() * * arguments: text - pointer to the buffer that has the expression. * * actions : calls getTok() to get tokens from the buffer. Places * tokens in a tempStack, and moves them into a list in * reverse-polish order. * * We need the list so that ALL syntax errors are caught * BEFORE processing of the expression begins (especially * program invocations that have side effects) * * Once the list is available, an operand stack is used * Items are popped and pushed from this stack by the * evaluation routines (add, mult, negate etc.) * * we don't really need a separate operand stack. the * tempStack has served its purpose when the list is * formed and so it may be used for operand processing. */ LOCAL BOOL NEAR handleExpr() { extern RPNINFO tokRec; BOOL fRParen; /* was the token got a right paren? */ extern BOOL done; extern RPNINFO *pTop, *pList; extern UCHAR errRow; extern UCHAR prevTok; pTop = tempStack; pList = rpnList; done = FALSE; errRow = 3; /* row for the first token put in,left paren*/ prevTok = LEFT_PAREN; type_and_val(LEFT_PAREN, 0L); *pTop = tokRec; while (!done) { /* while there are more tokens in buffer */ getTok(); fRParen = FALSE; if (tokRec.type != LEFT_PAREN) while (precVector[tokRec.type] <= precVector[pTop->type]) { if (!precVector[tokRec.type]) { /* if RIGHT_PAREN */ /* pop till a left paren is seen */ while (pTop->type != LEFT_PAREN) pushIntoList(); fRParen = TRUE; if (pTop < tempStack) makeError(line, SYNTAX_INVALID_EXPR); else { pTop--; /* pop the left paren */ break; } } else pushIntoList(); } /* while */ /* if token is a left paren, it has to go on the stack */ if (!fRParen) if (pTop == pEnd) makeError(line, EXPR_TOO_LONG_INTERNAL); else *++pTop = tokRec; } /* while */ /* check the stack here for not empty state */ // HACK: have to rewrite entire module // to avoid pointer arithmetic // pTop-- is potentially dangerous // if tempStack is near segment boundary if (pTop != tempStack - 1) makeError(line, SYNTAX_INVALID_EXPR); return(processList()); } /* --------------------------------------------------------------------------- * handleDefines() * * arguments: t pointer to buffer that has the identifier * * actions: Checks if one of 'ID' is present. * Aborts with error if more IDs present. * Is called for ifdef/ifndef/defined(ID). * * returns : TRUE if ID found in table. FALSE otherwise. * */ LOCAL BOOL NEAR handleDefines(t) char *t; { char *s; s = _ftcstok(t, " \t"); if (_ftcstok(NULL, " \t")) makeError(line, SYNTAX_UNEXPECTED_TOKEN, s); if (!s) makeError(line, MISSING_ARG_BEFORE_PAREN); if (findMacro(s)) return(TRUE); return(FALSE); } /* --------------------------------------------------------------------------- * handleExists() * * arguments: t pointer to buffer that has the identifier * * actions: Checks if 'name' is a valid file/directory * Aborts with error if more names present. * Is called for exist(name). * * returns : TRUE if ID found in table. FALSE otherwise. * */ LOCAL BOOL NEAR handleExists(t) char *t; { char *s; char *szUnQuoted; BOOL fResult = FALSE; s = _ftcstok(t, " \t"); if (_ftcstok(NULL, " \t")) makeError(line, SYNTAX_UNEXPECTED_TOKEN, s); //handle quoted names if (NULL == s || NULL == (szUnQuoted = unQuote(s))) makeError(line, MISSING_ARG_BEFORE_PAREN); if (!_access(szUnQuoted, 0x00)) { // existence check fResult = TRUE; } FREE(szUnQuoted); return(fResult); } /* --------------------------------------------------------------------------- * evalExpr() * * arguments: t pointer to buffer that has the expression * kind specifies if it is if/ifdef/ifndef etc. * * * * returns : TRUE if expression evaluates to true. * FALSE otherwise. */ BOOL NEAR evalExpr(t, kind) char *t; UCHAR kind; { if (!*t) makeError(line, SYNTAX_MISSING_DIRECTIVE); switch (kind) { case IFDEF_TYPE: case ELSE_IFDEF_TYPE: return(handleDefines(t)); case IFNDEF_TYPE: case ELSE_IFNDEF_TYPE: return((BOOL)!handleDefines(t)); default: text = t; return(handleExpr()); } }