#ifdef CONFIG_WITH_IF_CONDITIONALS /* $Id: expreval.c 3544 2022-01-29 02:22:03Z bird $ */ /** @file * expreval - Expressions evaluator, C / BSD make / nmake style. */ /* * Copyright (c) 2008-2010 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kBuild. If not, see * */ /******************************************************************************* * Header Files * *******************************************************************************/ #include "makeint.h" #include #include #include "filedef.h" #include "dep.h" #include "job.h" #include "commands.h" #include "variable.h" #include "rule.h" #include "debug.h" #include "hash.h" #include "version_compare.h" #include #ifndef _MSC_VER # include #endif #include /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** The max length of a string representation of a number. */ #define EXPR_NUM_LEN ((sizeof("-9223372036854775802") + 4) & ~3) /** The max operator stack depth. */ #define EXPR_MAX_OPERATORS 72 /** The max operand depth. */ #define EXPR_MAX_OPERANDS 128 /** Check if @a a_ch is a valid separator for a alphabetical binary * operator, omitting isspace. */ #define EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch) \ (ispunct((a_ch)) && (a_ch) != '@' && (a_ch) != '_') /** Check if @a a_ch is a valid separator for a alphabetical binary operator. */ #define EXPR_IS_OP_SEPARATOR(a_ch) \ (isspace((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch)) /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ /** The 64-bit signed integer type we're using. */ #ifdef _MSC_VER typedef __int64 EXPRINT64; #else # include typedef int64_t EXPRINT64; #endif /** Pointer to a evaluator instance. */ typedef struct EXPR *PEXPR; /** * Operand variable type. */ typedef enum { /** Invalid zero entry. */ kExprVar_Invalid = 0, /** A number. */ kExprVar_Num, /** A string in need of expanding (perhaps). */ kExprVar_String, /** A simple string that doesn't need expanding. */ kExprVar_SimpleString, /** A quoted string in need of expanding (perhaps). */ kExprVar_QuotedString, /** A simple quoted string that doesn't need expanding. */ kExprVar_QuotedSimpleString, /** The end of the valid variable types. */ kExprVar_End } EXPRVARTYPE; /** * Operand variable. */ typedef struct { /** The variable type. */ EXPRVARTYPE enmType; /** The variable. */ union { /** Pointer to the string. */ char *psz; /** The variable. */ EXPRINT64 i; } uVal; } EXPRVAR; /** Pointer to a operand variable. */ typedef EXPRVAR *PEXPRVAR; /** Pointer to a const operand variable. */ typedef EXPRVAR const *PCEXPRVAR; /** * Operator return statuses. */ typedef enum { kExprRet_Error = -1, kExprRet_Ok = 0, kExprRet_Operator, kExprRet_Operand, kExprRet_EndOfExpr, kExprRet_End } EXPRRET; /** * Operator. */ typedef struct { /** The operator. */ char szOp[11]; /** The length of the operator string. */ char cchOp; /** The pair operator. * This is used with '(' and '?'. */ char chPair; /** The precedence. Higher means higher. */ char iPrecedence; /** The number of arguments it takes. */ signed char cArgs; /** Pointer to the method implementing the operator. */ EXPRRET (*pfn)(PEXPR pThis); } EXPROP; /** Pointer to a const operator. */ typedef EXPROP const *PCEXPROP; /** * Expression evaluator instance. */ typedef struct EXPR { /** The full expression. */ const char *pszExpr; /** The current location. */ const char *psz; /** The current file location, used for errors. */ const floc *pFileLoc; /** Pending binary operator. */ PCEXPROP pPending; /** Top of the operator stack. */ int iOp; /** Top of the operand stack. */ int iVar; /** The operator stack. */ PCEXPROP apOps[EXPR_MAX_OPERATORS]; /** The operand stack. */ EXPRVAR aVars[EXPR_MAX_OPERANDS]; } EXPR; /******************************************************************************* * Global Variables * *******************************************************************************/ /** Operator start character map. * This indicates which characters that are starting operators and which aren't. * * Bit 0: Indicates that this char is used in operators. * Bit 1: When bit 0 is clear, this indicates whitespace. * When bit 1 is set, this indicates whether the operator can be used * immediately next to an operand without any clear separation. * Bits 2 thru 7: Index into g_aExprOps of the first operator starting with * this character. */ static unsigned char g_auchOpStartCharMap[256]; /** Whether we've initialized the map. */ static int g_fExprInitializedMap = 0; /******************************************************************************* * Internal Functions * *******************************************************************************/ static void expr_unget_op(PEXPR pThis); static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis); /** * Displays an error message. * * The total string length must not exceed 256 bytes. * * @param pThis The evaluator instance. * @param pszError The message format string. * @param ... The message format args. */ static void expr_error(PEXPR pThis, const char *pszError, ...) { char szTmp[256]; va_list va; va_start(va, pszError); vsprintf(szTmp, pszError, va); va_end(va); OS(fatal,pThis->pFileLoc, "%s", szTmp); } /** * Converts a number to a string. * * @returns pszDst. * @param pszDst The string buffer to write into. Assumes length of EXPR_NUM_LEN. * @param iSrc The number to convert. */ static char *expr_num_to_string(char *pszDst, EXPRINT64 iSrc) { static const char s_szDigits[17] = "0123456789abcdef"; char szTmp[EXPR_NUM_LEN]; char *psz = &szTmp[EXPR_NUM_LEN - 1]; int fNegative; fNegative = iSrc < 0; if (fNegative) { /** @todo this isn't right for INT64_MIN. */ iSrc = -iSrc; } *psz = '\0'; do { #if 0 *--psz = s_szDigits[iSrc & 0xf]; iSrc >>= 4; #else *--psz = s_szDigits[iSrc % 10]; iSrc /= 10; #endif } while (iSrc); #if 0 *--psz = 'x'; *--psz = '0'; #endif if (fNegative) *--psz = '-'; /* copy it into the output buffer. */ return (char *)memcpy(pszDst, psz, &szTmp[EXPR_NUM_LEN] - psz); } /** * Attempts to convert a (simple) string into a number. * * @returns status code. * @param pThis The evaluator instance. This is optional when fQuiet is true. * @param piSrc Where to store the numeric value on success. * @param pszSrc The string to try convert. * @param fQuiet Whether we should be quiet or grumpy on failure. */ static EXPRRET expr_string_to_num(PEXPR pThis, EXPRINT64 *piDst, const char *pszSrc, int fQuiet) { EXPRRET rc = kExprRet_Ok; char const *psz = pszSrc; EXPRINT64 i; unsigned uBase; int fNegative; /* * Skip blanks. */ while (ISBLANK(*psz)) psz++; /* * Check for '-'. * * At this point we will not need to deal with operators, this is * just an indicator of negative numbers. If some operator ends up * here it's because it came from a string expansion and thus shall * not be interpreted. If this turns out to be an stupid restriction * it can be fixed, but for now it stays like this. */ fNegative = *psz == '-'; if (fNegative) psz++; /* * Determin base. * * Recognize some exsotic prefixes here in addition to the two standard ones. */ if (*psz != '0') uBase = 10; else if (psz[1] == 'x' || psz[1] == 'X') { uBase = 16; psz += 2; } else if (psz[1] == 'b' || psz[1] == 'B') { uBase = 2; psz += 2; } else if (psz[1] == 'd' || psz[1] == 'D') { uBase = 10; psz += 2; } else if (psz[1] == 'o' || psz[1] == 'O') { uBase = 8; psz += 2; } else if (isdigit(psz[1]) && psz[1] != '9' && psz[1] != '8') { uBase = 8; psz++; } else uBase = 10; /* * Convert until we hit a non-digit. */ i = 0; for (;;) { unsigned iDigit; int ch = *psz; switch (ch) { case '0': iDigit = 0; break; case '1': iDigit = 1; break; case '2': iDigit = 2; break; case '3': iDigit = 3; break; case '4': iDigit = 4; break; case '5': iDigit = 5; break; case '6': iDigit = 6; break; case '7': iDigit = 7; break; case '8': iDigit = 8; break; case '9': iDigit = 9; break; case 'a': case 'A': iDigit = 10; break; case 'b': case 'B': iDigit = 11; break; case 'c': case 'C': iDigit = 12; break; case 'd': case 'D': iDigit = 13; break; case 'e': case 'E': iDigit = 14; break; case 'f': case 'F': iDigit = 15; break; default: /* is the rest white space? */ while (ISSPACE(*psz)) psz++; if (*psz != '\0') { iDigit = uBase; break; } /* fall thru */ case '\0': if (fNegative) i = -i; *piDst = i; return rc; } if (iDigit >= uBase) { if (fNegative) i = -i; *piDst = i; if (!fQuiet) expr_error(pThis, "Invalid number \"%.80s\"", pszSrc); return kExprRet_Error; } /* add the digit and advance */ i *= uBase; i += iDigit; psz++; } /* not reached */ } /** * Checks if the variable is a string or not. * * @returns 1 if it's a string, 0 otherwise. * @param pVar The variable. */ static int expr_var_is_string(PCEXPRVAR pVar) { return pVar->enmType >= kExprVar_String; } /** * Checks if the variable contains a string that was quoted * in the expression. * * @returns 1 if if was a quoted string, otherwise 0. * @param pVar The variable. */ static int expr_var_was_quoted(PCEXPRVAR pVar) { return pVar->enmType >= kExprVar_QuotedString; } /** * Deletes a variable. * * @param pVar The variable. */ static void expr_var_delete(PEXPRVAR pVar) { if (expr_var_is_string(pVar)) { free(pVar->uVal.psz); pVar->uVal.psz = NULL; } pVar->enmType = kExprVar_Invalid; } /** * Initializes a new variables with a sub-string value. * * @param pVar The new variable. * @param psz The start of the string value. * @param cch The number of chars to copy. * @param enmType The string type. */ static void expr_var_init_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType) { /* convert string needing expanding into simple ones if possible. */ if ( enmType == kExprVar_String && !memchr(psz, '$', cch)) enmType = kExprVar_SimpleString; else if ( enmType == kExprVar_QuotedString && !memchr(psz, '$', cch)) enmType = kExprVar_QuotedSimpleString; pVar->enmType = enmType; pVar->uVal.psz = xmalloc(cch + 1); memcpy(pVar->uVal.psz, psz, cch); pVar->uVal.psz[cch] = '\0'; } #if 0 /* unused */ /** * Initializes a new variables with a string value. * * @param pVar The new variable. * @param psz The string value. * @param enmType The string type. */ static void expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType) { expr_var_init_substring(pVar, psz, strlen(psz), enmType); } /** * Assigns a sub-string value to a variable. * * @param pVar The new variable. * @param psz The start of the string value. * @param cch The number of chars to copy. * @param enmType The string type. */ static void expr_var_assign_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType) { expr_var_delete(pVar); expr_var_init_substring(pVar, psz, cch, enmType); } /** * Assignes a string value to a variable. * * @param pVar The variable. * @param psz The string value. * @param enmType The string type. */ static void expr_var_assign_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType) { expr_var_delete(pVar); expr_var_init_string(pVar, psz, enmType); } #endif /* unused */ /** * Simplifies a string variable. * * @param pVar The variable. */ static void expr_var_make_simple_string(PEXPRVAR pVar) { switch (pVar->enmType) { case kExprVar_Num: { char *psz = (char *)xmalloc(EXPR_NUM_LEN); expr_num_to_string(psz, pVar->uVal.i); pVar->uVal.psz = psz; pVar->enmType = kExprVar_SimpleString; break; } case kExprVar_String: case kExprVar_QuotedString: { char *psz; assert(strchr(pVar->uVal.psz, '$')); psz = allocated_variable_expand(pVar->uVal.psz); free(pVar->uVal.psz); pVar->uVal.psz = psz; pVar->enmType = pVar->enmType == kExprVar_String ? kExprVar_SimpleString : kExprVar_QuotedSimpleString; break; } case kExprVar_SimpleString: case kExprVar_QuotedSimpleString: /* nothing to do. */ break; default: assert(0); } } #if 0 /* unused */ /** * Turns a variable into a string value. * * @param pVar The variable. */ static void expr_var_make_string(PEXPRVAR pVar) { switch (pVar->enmType) { case kExprVar_Num: expr_var_make_simple_string(pVar); break; case kExprVar_String: case kExprVar_SimpleString: case kExprVar_QuotedString: case kExprVar_QuotedSimpleString: /* nothing to do. */ break; default: assert(0); } } #endif /* unused */ /** * Initializes a new variables with a integer value. * * @param pVar The new variable. * @param i The integer value. */ static void expr_var_init_num(PEXPRVAR pVar, EXPRINT64 i) { pVar->enmType = kExprVar_Num; pVar->uVal.i = i; } /** * Assigns a integer value to a variable. * * @param pVar The variable. * @param i The integer value. */ static void expr_var_assign_num(PEXPRVAR pVar, EXPRINT64 i) { expr_var_delete(pVar); expr_var_init_num(pVar, i); } /** * Turns the variable into a number. * * @returns status code. * @param pThis The evaluator instance. * @param pVar The variable. */ static EXPRRET expr_var_make_num(PEXPR pThis, PEXPRVAR pVar) { switch (pVar->enmType) { case kExprVar_Num: /* nothing to do. */ break; case kExprVar_String: expr_var_make_simple_string(pVar); /* fall thru */ case kExprVar_SimpleString: { EXPRINT64 i; EXPRRET rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 0 /* fQuiet */); if (rc < kExprRet_Ok) return rc; expr_var_assign_num(pVar, i); break; } case kExprVar_QuotedString: case kExprVar_QuotedSimpleString: expr_error(pThis, "Cannot convert a quoted string to a number"); return kExprRet_Error; default: assert(0); return kExprRet_Error; } return kExprRet_Ok; } /** * Try to turn the variable into a number. * * @returns status code. * @param pVar The variable. */ static EXPRRET expr_var_try_make_num(PEXPRVAR pVar) { switch (pVar->enmType) { case kExprVar_Num: /* nothing to do. */ break; case kExprVar_String: expr_var_make_simple_string(pVar); /* fall thru */ case kExprVar_SimpleString: { EXPRINT64 i; EXPRRET rc = expr_string_to_num(NULL, &i, pVar->uVal.psz, 1 /* fQuiet */); if (rc < kExprRet_Ok) return rc; expr_var_assign_num(pVar, i); break; } default: assert(0); case kExprVar_QuotedString: case kExprVar_QuotedSimpleString: /* can't do this */ return kExprRet_Error; } return kExprRet_Ok; } /** * Initializes a new variables with a boolean value. * * @param pVar The new variable. * @param f The boolean value. */ static void expr_var_init_bool(PEXPRVAR pVar, int f) { pVar->enmType = kExprVar_Num; pVar->uVal.i = !!f; } /** * Assigns a boolean value to a variable. * * @param pVar The variable. * @param f The boolean value. */ static void expr_var_assign_bool(PEXPRVAR pVar, int f) { expr_var_delete(pVar); expr_var_init_bool(pVar, f); } /** * Turns the variable into an boolean. * * @returns the boolean interpretation. * @param pVar The variable. */ static int expr_var_make_bool(PEXPRVAR pVar) { switch (pVar->enmType) { case kExprVar_Num: pVar->uVal.i = !!pVar->uVal.i; break; case kExprVar_String: expr_var_make_simple_string(pVar); /* fall thru */ case kExprVar_SimpleString: { /* * Try convert it to a number. If that fails, use the * GNU make boolean logic - not empty string means true. */ EXPRINT64 iVal; char const *psz = pVar->uVal.psz; while (ISBLANK(*psz)) psz++; if ( *psz && expr_string_to_num(NULL, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok) expr_var_assign_bool(pVar, iVal != 0); else expr_var_assign_bool(pVar, *psz != '\0'); break; } case kExprVar_QuotedString: expr_var_make_simple_string(pVar); /* fall thru */ case kExprVar_QuotedSimpleString: /* * Use GNU make boolean logic (not empty string means true). * No stripping here, the string is quoted. */ expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0'); break; default: assert(0); break; } return pVar->uVal.i; } /** * Pops a varable off the stack and deletes it. * @param pThis The evaluator instance. */ static void expr_pop_and_delete_var(PEXPR pThis) { expr_var_delete(&pThis->aVars[pThis->iVar]); pThis->iVar--; } /** * Tries to make the variables the same type. * * This will not convert numbers to strings, unless one of them * is a quoted string. * * this will try convert both to numbers if neither is quoted. Both * conversions will have to suceed for this to be commited. * * All strings will be simplified. * * @returns status code. Done complaining on failure. * * @param pThis The evaluator instance. * @param pVar1 The first variable. * @param pVar2 The second variable. */ static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp) { /* * Try make the variables the same type before comparing. */ if ( !expr_var_was_quoted(pVar1) && !expr_var_was_quoted(pVar2)) { if ( expr_var_is_string(pVar1) || expr_var_is_string(pVar2)) { if (!expr_var_is_string(pVar1)) expr_var_try_make_num(pVar2); else if (!expr_var_is_string(pVar2)) expr_var_try_make_num(pVar1); else { /* * Both are strings, simplify them then see if both can be made into numbers. */ EXPRINT64 iVar1; EXPRINT64 iVar2; expr_var_make_simple_string(pVar1); expr_var_make_simple_string(pVar2); if ( expr_string_to_num(NULL, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok && expr_string_to_num(NULL, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok) { expr_var_assign_num(pVar1, iVar1); expr_var_assign_num(pVar2, iVar2); } } } } else { expr_var_make_simple_string(pVar1); expr_var_make_simple_string(pVar2); } /* * Complain if they aren't the same type now. */ if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2)) { expr_error(pThis, "Unable to unify types for \"%s\"", pszOp); return kExprRet_Error; } return kExprRet_Ok; } /** * Is variable defined, unary. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_defined(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; struct variable *pMakeVar; expr_var_make_simple_string(pVar); pMakeVar = lookup_variable(pVar->uVal.psz, strlen(pVar->uVal.psz)); expr_var_assign_bool(pVar, pMakeVar && *pMakeVar->value != '\0'); return kExprRet_Ok; } /** * Does file(/dir/whatever) exist, unary. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_exists(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; struct stat st; expr_var_make_simple_string(pVar); expr_var_assign_bool(pVar, stat(pVar->uVal.psz, &st) == 0); return kExprRet_Ok; } /** * Is target defined, unary. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_target(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; struct file *pFile = NULL; /* * Because of secondary target expansion, lookup the unexpanded * name first. */ #ifdef CONFIG_WITH_2ND_TARGET_EXPANSION if ( pVar->enmType == kExprVar_String || pVar->enmType == kExprVar_QuotedString) { pFile = lookup_file(pVar->uVal.psz); if ( pFile && !pFile->need_2nd_target_expansion) pFile = NULL; } if (!pFile) #endif { expr_var_make_simple_string(pVar); pFile = lookup_file(pVar->uVal.psz); } /* * Always inspect the head of a multiple target rule * and look for a file with commands. */ #ifdef CONFIG_WITH_EXPLICIT_MULTITARGET if (pFile && pFile->multi_head) pFile = pFile->multi_head; #endif while (pFile && !pFile->cmds) pFile = pFile->prev; expr_var_assign_bool(pVar, pFile != NULL && pFile->is_target); return kExprRet_Ok; } /** * Convert to boolean. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_bool(PEXPR pThis) { expr_var_make_bool(&pThis->aVars[pThis->iVar]); return kExprRet_Ok; } /** * Convert to number, works on quoted strings too. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_num(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; /* unquote the string */ if (pVar->enmType == kExprVar_QuotedSimpleString) pVar->enmType = kExprVar_SimpleString; else if (pVar->enmType == kExprVar_QuotedString) pVar->enmType = kExprVar_String; return expr_var_make_num(pThis, pVar); } /** * Performs a strlen() on the simplified/converted string argument. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_strlen(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; expr_var_make_simple_string(pVar); expr_var_assign_num(pVar, strlen(pVar->uVal.psz)); return kExprRet_Ok; } /** * Convert to string (simplified and quoted) * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_str(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; expr_var_make_simple_string(pVar); pVar->enmType = kExprVar_QuotedSimpleString; return kExprRet_Ok; } /** * Pluss (dummy / make_integer) * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_pluss(PEXPR pThis) { return expr_var_make_num(pThis, &pThis->aVars[pThis->iVar]); } /** * Minus (negate) * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_minus(PEXPR pThis) { EXPRRET rc; PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar); if (rc >= kExprRet_Ok) pVar->uVal.i = -pVar->uVal.i; return rc; } /** * Bitwise NOT. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_bitwise_not(PEXPR pThis) { EXPRRET rc; PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar); if (rc >= kExprRet_Ok) pVar->uVal.i = ~pVar->uVal.i; return rc; } /** * Logical NOT. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_logical_not(PEXPR pThis) { PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; expr_var_make_bool(pVar); pVar->uVal.i = !pVar->uVal.i; return kExprRet_Ok; } /** * Multiplication. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_multiply(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i *= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Division. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_divide(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i /= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Modulus. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_modulus(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i %= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Addition (numeric). * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_add(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i += pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Subtract (numeric). * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_sub(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i -= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Bitwise left shift. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_shift_left(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i <<= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Bitwise right shift. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_shift_right(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i >>= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return rc; } /** * Less than or equal, version string. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_less_or_equal_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "vle"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i); else expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) <= 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Less than or equal. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_less_or_equal_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "<="); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i); else expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) <= 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Less than, version string. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_less_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "vlt"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i); else expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) < 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Less than. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_less_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "<"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i); else expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) < 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Greater or equal than, version string. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_greater_or_equal_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "vge"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i); else expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) >= 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Greater or equal than. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_greater_or_equal_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, ">="); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i); else expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) >= 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Greater than, version string. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_greater_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, "vgt"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i); else expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) > 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Greater than. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_greater_than(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; rc = expr_var_unify_types(pThis, pVar1, pVar2, ">"); if (rc >= kExprRet_Ok) { if (!expr_var_is_string(pVar1)) expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i); else expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) > 0); } expr_pop_and_delete_var(pThis); return rc; } /** * Equal, version strings. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_equal(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; int const fIsString1 = expr_var_is_string(pVar1); /* * The same type? */ if (fIsString1 == expr_var_is_string(pVar2)) { if (!fIsString1) /* numbers are simple */ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); else { /* try a normal string compare. */ expr_var_make_simple_string(pVar1); expr_var_make_simple_string(pVar2); if (!version_compare(pVar1->uVal.psz, pVar2->uVal.psz)) expr_var_assign_bool(pVar1, 1); /* try convert and compare as number instead. */ else if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok && expr_var_try_make_num(pVar2) >= kExprRet_Ok) expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); /* ok, they really aren't equal. */ else expr_var_assign_bool(pVar1, 0); } } else { /* * If the type differs, there are now two options: * 1. Try convert the string to a valid number and compare the numbers. * 2. Convert the non-string to a number and compare the strings. */ if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok && expr_var_try_make_num(pVar2) >= kExprRet_Ok) expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); else { expr_var_make_simple_string(pVar1); expr_var_make_simple_string(pVar2); expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) == 0); } } expr_pop_and_delete_var(pThis); return rc; } /** * Not equal, version string. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_ver_not_equal(PEXPR pThis) { EXPRRET rc = expr_op_ver_equal(pThis); if (rc >= kExprRet_Ok) rc = expr_op_logical_not(pThis); return rc; } /** * Equal. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_equal(PEXPR pThis) { EXPRRET rc = kExprRet_Ok; PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; int const fIsString1 = expr_var_is_string(pVar1); /* * The same type? */ if (fIsString1 == expr_var_is_string(pVar2)) { if (!fIsString1) /* numbers are simple */ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); else { /* try a normal string compare. */ expr_var_make_simple_string(pVar1); expr_var_make_simple_string(pVar2); if (!strcmp(pVar1->uVal.psz, pVar2->uVal.psz)) expr_var_assign_bool(pVar1, 1); /* try convert and compare as number instead. */ else if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok && expr_var_try_make_num(pVar2) >= kExprRet_Ok) expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); /* ok, they really aren't equal. */ else expr_var_assign_bool(pVar1, 0); } } else { /* * If the type differs, there are now two options: * 1. Convert the string to a valid number and compare the numbers. * 2. Convert an empty string to a 'false' boolean value and compare * numerically. This one is a bit questionable, so we don't try this. */ if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok && expr_var_try_make_num(pVar2) >= kExprRet_Ok) expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); else { expr_error(pThis, "Cannot compare strings and numbers"); rc = kExprRet_Error; } } expr_pop_and_delete_var(pThis); return rc; } /** * Not equal. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_not_equal(PEXPR pThis) { EXPRRET rc = expr_op_equal(pThis); if (rc >= kExprRet_Ok) rc = expr_op_logical_not(pThis); return rc; } /** * Bitwise AND. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_bitwise_and(PEXPR pThis) { PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; EXPRRET rc; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i &= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return kExprRet_Ok; } /** * Bitwise XOR. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_bitwise_xor(PEXPR pThis) { PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; EXPRRET rc; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i ^= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return kExprRet_Ok; } /** * Bitwise OR. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_bitwise_or(PEXPR pThis) { PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; EXPRRET rc; rc = expr_var_make_num(pThis, pVar1); if (rc >= kExprRet_Ok) { rc = expr_var_make_num(pThis, pVar2); if (rc >= kExprRet_Ok) pVar1->uVal.i |= pVar2->uVal.i; } expr_pop_and_delete_var(pThis); return kExprRet_Ok; } /** * Logical AND. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_logical_and(PEXPR pThis) { PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; if ( expr_var_make_bool(pVar1) && expr_var_make_bool(pVar2)) expr_var_assign_bool(pVar1, 1); else expr_var_assign_bool(pVar1, 0); expr_pop_and_delete_var(pThis); return kExprRet_Ok; } /** * Logical OR. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_logical_or(PEXPR pThis) { PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; if ( expr_var_make_bool(pVar1) || expr_var_make_bool(pVar2)) expr_var_assign_bool(pVar1, 1); else expr_var_assign_bool(pVar1, 0); expr_pop_and_delete_var(pThis); return kExprRet_Ok; } /** * Left parenthesis. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_left_parenthesis(PEXPR pThis) { /* * There should be a right parenthesis operator lined up for us now, * eat it. If not found there is an inbalance. */ EXPRRET rc = expr_get_binary_or_eoe_or_rparen(pThis); if ( rc == kExprRet_Operator && pThis->apOps[pThis->iOp]->szOp[0] == ')') { /* pop it and get another one which we can leave pending. */ pThis->iOp--; rc = expr_get_binary_or_eoe_or_rparen(pThis); if (rc >= kExprRet_Ok) expr_unget_op(pThis); } else { expr_error(pThis, "Missing ')'"); rc = kExprRet_Error; } return rc; } /** * Right parenthesis, dummy that's never actually called. * * @returns Status code. * @param pThis The instance. */ static EXPRRET expr_op_right_parenthesis(PEXPR pThis) { assert(0); (void)pThis; return kExprRet_Ok; } /** * The operator table. * * This table is NOT ordered by precedence, but for linear search * allowing for first match to return the correct operator. This * means that || must come before |, or else | will match all. */ static const EXPROP g_aExprOps[] = { #define EXPR_OP(szOp, iPrecedence, cArgs, pfn) { szOp, sizeof(szOp) - 1, '\0', iPrecedence, cArgs, pfn } /* Name, iPrecedence, cArgs, pfn */ EXPR_OP("defined", 90, 1, expr_op_defined), EXPR_OP("exists", 90, 1, expr_op_exists), EXPR_OP("target", 90, 1, expr_op_target), EXPR_OP("bool", 90, 1, expr_op_bool), EXPR_OP("num", 90, 1, expr_op_num), EXPR_OP("strlen", 90, 1, expr_op_strlen), EXPR_OP("str", 90, 1, expr_op_str), EXPR_OP("+", 80, 1, expr_op_pluss), EXPR_OP("-", 80, 1, expr_op_minus), EXPR_OP("~", 80, 1, expr_op_bitwise_not), EXPR_OP("*", 75, 2, expr_op_multiply), EXPR_OP("/", 75, 2, expr_op_divide), EXPR_OP("%", 75, 2, expr_op_modulus), EXPR_OP("+", 70, 2, expr_op_add), EXPR_OP("-", 70, 2, expr_op_sub), EXPR_OP("<<", 65, 2, expr_op_shift_left), EXPR_OP(">>", 65, 2, expr_op_shift_right), EXPR_OP("<=", 60, 2, expr_op_less_or_equal_than), EXPR_OP("<", 60, 2, expr_op_less_than), EXPR_OP(">=", 60, 2, expr_op_greater_or_equal_than), EXPR_OP(">", 60, 2, expr_op_greater_than), EXPR_OP("vle", 60, 2, expr_op_ver_less_or_equal_than), EXPR_OP("vlt", 60, 2, expr_op_ver_less_than), EXPR_OP("vge", 60, 2, expr_op_ver_greater_or_equal_than), EXPR_OP("vgt", 60, 2, expr_op_ver_greater_than), EXPR_OP("==", 55, 2, expr_op_equal), EXPR_OP("veq", 55, 2, expr_op_ver_equal), EXPR_OP("!=", 55, 2, expr_op_not_equal), EXPR_OP("vne", 55, 2, expr_op_ver_not_equal), EXPR_OP("!", 80, 1, expr_op_logical_not), EXPR_OP("^", 45, 2, expr_op_bitwise_xor), EXPR_OP("&&", 35, 2, expr_op_logical_and), EXPR_OP("&", 50, 2, expr_op_bitwise_and), EXPR_OP("||", 30, 2, expr_op_logical_or), EXPR_OP("|", 40, 2, expr_op_bitwise_or), { "(", 1, ')', 10, 1, expr_op_left_parenthesis }, { ")", 1, '(', 10, 0, expr_op_right_parenthesis }, /* { "?", 1, ':', 5, 2, expr_op_question }, { ":", 1, '?', 5, 2, expr_op_colon }, -- too weird for now. */ #undef EXPR_OP }; /** Dummy end of expression fake. */ static const EXPROP g_ExprEndOfExpOp = { "", 0, '\0', 0, 0, NULL }; /** * Initializes the opcode character map if necessary. */ static void expr_map_init(void) { unsigned i; if (g_fExprInitializedMap) return; /* * Initialize it. */ memset(&g_auchOpStartCharMap, 0, sizeof(g_auchOpStartCharMap)); for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++) { unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0]; if (!g_auchOpStartCharMap[ch]) { g_auchOpStartCharMap[ch] = (i << 2) | 1; if (!isalpha(ch)) g_auchOpStartCharMap[ch] |= 2; /* Need no clear separation from operands. */ } } /* whitespace (assumes C-like locale because I'm lazy): */ #define SET_WHITESPACE(a_ch) do { \ assert(g_auchOpStartCharMap[(unsigned char)(a_ch)] == 0); \ g_auchOpStartCharMap[(unsigned char)(a_ch)] |= 2; \ } while (0) SET_WHITESPACE(' '); SET_WHITESPACE('\t'); SET_WHITESPACE('\n'); SET_WHITESPACE('\r'); SET_WHITESPACE('\v'); SET_WHITESPACE('\f'); g_fExprInitializedMap = 1; } /** * Looks up a character in the map. * * @returns the value for that char, see g_auchOpStartCharMap for details. * @param ch The character. */ static unsigned char expr_map_get(char ch) { return g_auchOpStartCharMap[(unsigned char)ch]; } /** * Searches the operator table given a potential operator start char. * * @returns Pointer to the matching operator. NULL if not found. * @param psz Pointer to what can be an operator. * @param uchVal The expr_map_get value. * @param fUnary Whether it must be an unary operator or not. */ static PCEXPROP expr_lookup_op(char const *psz, unsigned char uchVal, int fUnary) { char ch = *psz; unsigned i; assert((uchVal & 2) == (isalpha(ch) ? 0 : 2)); for (i = uchVal >> 2; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++) { /* compare the string... */ if (g_aExprOps[i].szOp[0] != ch) continue; switch (g_aExprOps[i].cchOp) { case 1: break; case 2: if (g_aExprOps[i].szOp[1] != psz[1]) continue; break; default: if (strncmp(&g_aExprOps[i].szOp[1], psz + 1, g_aExprOps[i].cchOp - 1)) continue; break; } /* ... and the operator type. */ if (fUnary == (g_aExprOps[i].cArgs == 1)) { /* Check if we've got the needed operand separation: */ if ( (uchVal & 2) || EXPR_IS_OP_SEPARATOR(psz[g_aExprOps[i].cchOp])) { /* got a match! */ return &g_aExprOps[i]; } } } return NULL; } /** * Ungets a binary operator. * * The operator is poped from the stack and put in the pending position. * * @param pThis The evaluator instance. */ static void expr_unget_op(PEXPR pThis) { assert(pThis->pPending == NULL); assert(pThis->iOp >= 0); pThis->pPending = pThis->apOps[pThis->iOp]; pThis->apOps[pThis->iOp] = NULL; pThis->iOp--; } /** * Get the next token, it should be a binary operator, or the end of * the expression, or a right parenthesis. * * The operator is pushed onto the stack and the status code indicates * which of the two we found. * * @returns status code. Will grumble on failure. * @retval kExprRet_EndOfExpr if we encountered the end of the expression. * @retval kExprRet_Operator if we encountered a binary operator or right * parenthesis. It's on the operator stack. * * @param pThis The evaluator instance. */ static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis) { /* * See if there is anything pending first. */ PCEXPROP pOp = pThis->pPending; if (pOp) pThis->pPending = NULL; else { /* * Eat more of the expression. */ char const *psz = pThis->psz; /* spaces */ unsigned char uchVal; char ch; while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2) psz++; /* see what we've got. */ if (ch) { if (uchVal & 1) pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */); if (!pOp) { expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz); return kExprRet_Error; } psz += pOp->cchOp; } else pOp = &g_ExprEndOfExpOp; pThis->psz = psz; } /* * Push it. */ if (pThis->iOp >= EXPR_MAX_OPERATORS - 1) { expr_error(pThis, "Operator stack overflow"); return kExprRet_Error; } pThis->apOps[++pThis->iOp] = pOp; return pOp->iPrecedence ? kExprRet_Operator : kExprRet_EndOfExpr; } /** * Get the next token, it should be an unary operator or an operand. * * This will fail if encountering the end of the expression since * it is implied that there should be something more. * * The token is pushed onto the respective stack and the status code * indicates which it is. * * @returns status code. On failure we'll be done bitching already. * @retval kExprRet_Operator if we encountered an unary operator. * It's on the operator stack. * @retval kExprRet_Operand if we encountered an operand operator. * It's on the operand stack. * * @param This The evaluator instance. */ static EXPRRET expr_get_unary_or_operand(PEXPR pThis) { EXPRRET rc; unsigned char uchVal; PCEXPROP pOp; char const *psz = pThis->psz; char ch; /* * Eat white space and make sure there is something after it. */ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2) psz++; if (ch == '\0') { expr_error(pThis, "Unexpected end of expression"); return kExprRet_Error; } /* * Is it an operator? */ pOp = NULL; if (uchVal & 1) pOp = expr_lookup_op(psz, uchVal, 1 /* fUnary */); if (pOp) { /* * Push the operator onto the stack. */ if (pThis->iVar < EXPR_MAX_OPERANDS - 1) { pThis->apOps[++pThis->iOp] = pOp; rc = kExprRet_Operator; } else { expr_error(pThis, "Operator stack overflow"); rc = kExprRet_Error; } psz += pOp->cchOp; } else if (pThis->iVar < EXPR_MAX_OPERANDS - 1) { /* * It's an operand. Figure out where it ends and * push it onto the stack. */ const char *pszStart; rc = kExprRet_Ok; if (ch == '"') { pszStart = ++psz; while ((ch = *psz) != '\0' && ch != '"') psz++; expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString); if (ch != '\0') psz++; } else if (ch == '\'') { pszStart = ++psz; while ((ch = *psz) != '\0' && ch != '\'') psz++; expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedSimpleString); if (ch != '\0') psz++; } else { char achPars[20]; int iPar = -1; char chEndPar = '\0'; pszStart = psz; while ((ch = *psz) != '\0') { char ch2; /* $(adsf) or ${asdf} needs special handling. */ if ( ch == '$' && ( (ch2 = psz[1]) == '(' || ch2 == '{')) { psz++; if (iPar > (int)(sizeof(achPars) / sizeof(achPars[0]))) { expr_error(pThis, "Too deep nesting of variable expansions"); rc = kExprRet_Error; break; } achPars[++iPar] = chEndPar = ch2 == '(' ? ')' : '}'; } else if (ch == chEndPar) { iPar--; chEndPar = iPar >= 0 ? achPars[iPar] : '\0'; } else if (!chEndPar) { uchVal = expr_map_get(ch); if (uchVal == 0) { /*likely*/ } else if ((uchVal & 3) == 2 /*isspace*/) break; else if ( (uchVal & 1) && psz != pszStart /* not at the start */ && ( (uchVal & 2) /* operator without separator needs */ || EXPR_IS_OP_SEPARATOR_NO_SPACE(psz[-1]))) { pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */); if (pOp) break; } } /* next */ psz++; } if (rc == kExprRet_Ok) expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String); } } else { expr_error(pThis, "Operand stack overflow"); rc = kExprRet_Error; } pThis->psz = psz; return rc; } /** * Evaluates the current expression. * * @returns status code. * * @param pThis The instance. */ static EXPRRET expr_eval(PEXPR pThis) { EXPRRET rc; PCEXPROP pOp; /* * The main loop. */ for (;;) { /* * Eat unary operators until we hit an operand. */ do rc = expr_get_unary_or_operand(pThis); while (rc == kExprRet_Operator); if (rc < kExprRet_Ok) break; /* * Look for a binary operator, right parenthesis or end of expression. */ rc = expr_get_binary_or_eoe_or_rparen(pThis); if (rc < kExprRet_Ok) break; expr_unget_op(pThis); /* * Pop operators and apply them. * * Parenthesis will be handed via precedence, where the left parenthesis * will go pop the right one and make another operator pending. */ while ( pThis->iOp >= 0 && pThis->apOps[pThis->iOp]->iPrecedence >= pThis->pPending->iPrecedence) { pOp = pThis->apOps[pThis->iOp--]; assert(pThis->iVar + 1 >= pOp->cArgs); rc = pOp->pfn(pThis); if (rc < kExprRet_Ok) break; } if (rc < kExprRet_Ok) break; /* * Get the next binary operator or end of expression. * There should be no right parenthesis here. */ rc = expr_get_binary_or_eoe_or_rparen(pThis); if (rc < kExprRet_Ok) break; pOp = pThis->apOps[pThis->iOp]; if (!pOp->iPrecedence) break; /* end of expression */ if (!pOp->cArgs) { expr_error(pThis, "Unexpected \"%s\"", pOp->szOp); rc = kExprRet_Error; break; } } return rc; } /** * Destroys the given instance. * * @param pThis The instance to destroy. */ static void expr_destroy(PEXPR pThis) { while (pThis->iVar >= 0) { expr_var_delete(pThis->aVars); pThis->iVar--; } free(pThis); } /** * Instantiates an expression evaluator. * * @returns The instance. * * @param pszExpr What to parse. * This must stick around until expr_destroy. */ static PEXPR expr_create(char const *pszExpr) { PEXPR pThis = (PEXPR)xmalloc(sizeof(*pThis)); pThis->pszExpr = pszExpr; pThis->psz = pszExpr; pThis->pFileLoc = NULL; pThis->pPending = NULL; pThis->iVar = -1; pThis->iOp = -1; expr_map_init(); return pThis; } /** * Evaluates the given if expression. * * @returns -1, 0 or 1. (GNU make conditional check convention, see read.c.) * @retval -1 if the expression is invalid. * @retval 0 if the expression is true * @retval 1 if the expression is false. * * @param line The expression. * @param flocp The file location, used for errors. */ int expr_eval_if_conditionals(const char *line, const floc *flocp) { /* * Instantiate the expression evaluator and let * it have a go at it. */ int rc = -1; PEXPR pExpr = expr_create(line); pExpr->pFileLoc = flocp; if (expr_eval(pExpr) >= kExprRet_Ok) { /* * Convert the result (on top of the stack) to boolean and * set our return value accordingly. */ if (expr_var_make_bool(&pExpr->aVars[0])) rc = 0; else rc = 1; } expr_destroy(pExpr); return rc; } /** * Evaluates the given expression and returns the result as a string. * * @returns variable buffer position. * * @param o The current variable buffer position. * @param expr The expression. */ char *expr_eval_to_string(char *o, const char *expr) { /* * Instantiate the expression evaluator and let * it have a go at it. */ PEXPR pExpr = expr_create(expr); if (expr_eval(pExpr) >= kExprRet_Ok) { /* * Convert the result (on top of the stack) to a string * and copy it out the variable buffer. */ PEXPRVAR pVar = &pExpr->aVars[0]; expr_var_make_simple_string(pVar); o = variable_buffer_output(o, pVar->uVal.psz, strlen(pVar->uVal.psz)); } else o = variable_buffer_output(o, "", sizeof("") - 1); expr_destroy(pExpr); return o; } #endif /* CONFIG_WITH_IF_CONDITIONALS */