From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Runtime/common/misc/expreval.cpp | 2740 +++++++++++++++++++++++++++++ 1 file changed, 2740 insertions(+) create mode 100644 src/VBox/Runtime/common/misc/expreval.cpp (limited to 'src/VBox/Runtime/common/misc/expreval.cpp') diff --git a/src/VBox/Runtime/common/misc/expreval.cpp b/src/VBox/Runtime/common/misc/expreval.cpp new file mode 100644 index 00000000..1099db46 --- /dev/null +++ b/src/VBox/Runtime/common/misc/expreval.cpp @@ -0,0 +1,2740 @@ +/* $Id: expreval.cpp $ */ +/** @file + * expreval - Expressions evaluator. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program 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, in version 3 of the + * License. + * + * This program 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 this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "internal/iprt.h" +#include + +#include +#include +#include +#include +#include +#include +#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 +/** the max variable recursion. */ +#define EXPR_MAX_VAR_RECURSION 20 + +/** 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) \ + (RT_C_IS_PUNCT((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) \ + (RT_C_IS_SPACE((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch)) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The 64-bit signed integer type we're using. */ +typedef int64_t EXPRINT64; + +/** 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. */ + uint8_t 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; + + +/** Magic value for RTEXPREVALINT::u32Magic. + * @todo fixme */ +#define RTEXPREVAL_MAGIC UINT32_C(0x12345678) + +/** + * Expression evaluator instance. + */ +typedef struct RTEXPREVALINT +{ + /** Magic number (RTEXPREVAL_MAGIC). */ + uint32_t u32Magic; + /** Reference counter. */ + uint32_t volatile cRefs; + /** RTEXPREVAL_XXX. */ + uint64_t fFlags; + /** Name for logging purposes (copy) */ + char *pszName; + /** User argument to callbacks. */ + void *pvUser; + /** Callback for getting variables or checking if they exists. */ + PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable; +} RTEXPREVALINT; + +/** + * An expression being evaluated. + */ +typedef struct EXPR +{ + /** The full expression. */ + const char *pszExpr; + /** The current location. */ + const char *psz; + /** Error info keeper. */ + PRTERRINFO pErrInfo; + /** Pointer to the instance we evaluating under. */ + RTEXPREVALINT *pEvaluator; + /** 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 uint8_t g_abOpStartCharMap[256] = {0}; +/** 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. + * + * @returns kExprRet_Error + * @param pThis The evaluator instance. + * @param pszError The message format string. + * @param ... The message format args. + */ +static EXPRRET expr_error(PEXPR pThis, const char *pszError, ...) +{ + va_list va; + va_start(va, pszError); + RTErrInfoSetV(pThis->pErrInfo, VERR_PARSE_ERROR, pszError, va); + va_end(va); + return kExprRet_Error; +} + + +/** + * 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) +{ + char szTmp[64]; /* RTStrFormatNumber assumes this as a minimum size. */ + AssertCompile(EXPR_NUM_LEN < sizeof(szTmp)); + size_t cchTmp = RTStrFormatNumber(szTmp, iSrc, 10 /*uBase*/, 0 /*cchWidth*/, 0 /*cchPrecision*/, + RTSTR_F_64BIT | RTSTR_F_VALSIGNED); + return (char *)memcpy(pszDst, szTmp, cchTmp + 1); +} + + +/** + * Attempts to convert a (simple) string into a number. + * + * @returns status code. + * @param pThis The evaluator instance. + * @param piDst 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 (RT_C_IS_BLANK(*psz)) + psz++; + const char *const pszFirst = 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. + */ + uint64_t const fFlags = pThis->pEvaluator->fFlags; + uBase = fFlags & RTEXPREVAL_F_DEFAULT_BASE_16 ? 16 : 10; + char const ch0 = psz[0]; + if (ch0 == '0') + { + char const ch1 = psz[1]; + switch (ch1) + { + case '\0': + break; + + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* C-style octal */ + if (fFlags & RTEXPREVAL_F_C_OCTAL) + { + uBase = 8; + psz++; + } + break; + + case '8': + case '9': + break; + + case 'x': + case 'X': + uBase = 16; + psz += 2; + break; + case 'y': case 'Y': /* windbg, VBoxDbg */ + case 'b': case 'B': /* python and others */ + uBase = 2; + psz += 2; + break; + case 'n': case 'N': /* windbg */ + case 'i': case 'I': /* VBoxDbg */ + uBase = 10; + psz += 2; + break; + case 't': case 'T': /* windbg, VBoxDbg */ + case 'o': case 'O': /* python and others */ + uBase = 8; + psz += 2; + break; + } + } + + /* + * Convert digits. + */ + 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': iDigit = 15; break; + case 'f': + /* Make 'false' -> 0: */ + if ( psz != pszFirst + || strncmp(psz + 1, RT_STR_TUPLE("alse")) != 0) + { + iDigit = 15; + break; + } + psz += sizeof("false") - 1; + RT_FALL_THROUGH(); + + default: + /* Make 'true' evaluate to 1: */ + if (psz == pszFirst && strncmp(psz, RT_STR_TUPLE("true")) == 0) + { + psz += sizeof("true") - 1; + i = 1; + } + + /* + * Is the rest white space? + */ + while (RT_C_IS_SPACE(*psz)) + psz++; + if (*psz != '\0') + { + iDigit = uBase; + break; + } + RT_FALL_THROUGH(); + + 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 %u-base number \"%.80s\"", uBase, pszSrc); + return kExprRet_Error; + } + + /* add the digit and advance */ + /** @todo check for overflow? */ + 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)) + { + RTMemTmpFree(pVar->uVal.psz); + pVar->uVal.psz = NULL; + } + pVar->enmType = kExprVar_Invalid; +} + + +/** + * Initializes a new variables with a sub-string value. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @param pThis The evaluator expression instance. + * @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 EXPRRET expr_var_init_substring(PEXPR pThis, 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 = (char *)RTMemTmpAlloc(cch + 1); + if (RT_LIKELY(pVar->uVal.psz)) + { + memcpy(pVar->uVal.psz, psz, cch); + pVar->uVal.psz[cch] = '\0'; + return kExprRet_Ok; + } + pVar->enmType = kExprVar_End; + RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cch + 1); + return kExprRet_Error; +} + + +#if 0 /* unused */ +/** + * Initializes a new variables with a string value. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @param pVar The new variable. + * @param psz The string value. + * @param enmType The string type. + */ +static EXPRRET expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType) +{ + return expr_var_init_substring(pVar, psz, strlen(psz), enmType); +} + + +/** + * Assigns a sub-string value to a variable. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @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); + return expr_var_init_substring(pVar, psz, cch, enmType); +} + + +/** + * Assignes a string value to a variable. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @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); + return expr_var_init_string(pVar, psz, enmType); +} +#endif /* unused */ + + +/** + * Finds the end of the current variable expansion, taking nested expansion + * into account. + * + * This is somewhat similar to the code down in expr_get_unary_or_operand. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @param pThis The evaluator expression instance. + * @param pchSrc Pointer to the dollar of the variable expansion. + * @param cchSrc The length of the variable expansion expression. + * @param pcchVarRef Where to return the length of the variable expansion. + * @param pfNested Where to return whether it's a nested (@c true) or plain + * one. + */ +static EXPRRET expr_expand_find_end(PEXPR pThis, const char *pchSrc, size_t cchSrc, size_t *pcchVarRef, bool *pfNested) +{ + const char * const pchStart = pchSrc; + + /* + * Push the initial expression. + */ + Assert(cchSrc >= 2); + Assert(pchSrc[0] == '$'); + Assert(pchSrc[1] == '{'); + unsigned cPars = 1; + pchSrc += 2; + cchSrc -= 2; + + /* + * Parse the rest of the string till we've back at cPars == 0. + */ + *pfNested = false; + while (cchSrc > 0) + { + char const ch = *pchSrc; + if ( ch == '$' + && cchSrc >= 2 + && pchSrc[1] == '{') + { + if (cPars < EXPR_MAX_VAR_RECURSION) + cPars++; + else + { + *pcchVarRef = 0; + return expr_error(pThis, "Too deep nesting of variable expansions"); + } + *pfNested = true; + pchSrc += 2; + cchSrc -= 2; + } + else + { + pchSrc += 1; + cchSrc -= 1; + if (ch == '}') + if (--cPars == 0) + { + *pcchVarRef = pchSrc - pchStart; + return kExprRet_Ok; + } + } + } + *pcchVarRef = 0; + return expr_error(pThis, "Unbalanced variable expansions: %.*s", pchStart, pchSrc - pchStart); +} + + +/** + * Returns the given string with all variables references replaced. + * + * @returns Pointer to expanded string on success (RTMemTmpFree), NULL on + * failure (error already set). + * @param pThis The evaluator expression instance. + * @param pchSrc The string to expand. + * @param cchSrc The length of the string to expand. + * @param cDepth The recursion depth, starting at zero. + */ +static char *expr_expand_string(PEXPR pThis, const char *pchSrc, size_t cchSrc, unsigned cDepth) +{ + if (cDepth < EXPR_MAX_VAR_RECURSION) + { + size_t cbRetAlloc = RT_ALIGN_Z(cchSrc + 1 + 16, 16); + char *pszRet = (char *)RTMemTmpAlloc(cbRetAlloc); + if (pszRet) + { + size_t offRet = 0; + while (cchSrc > 0) + { + /* + * Look for the next potential variable reference. + */ + const char *pchDollar = (const char *)memchr(pchSrc, '$', cchSrc); + size_t cchPlain = pchDollar ? pchDollar - pchSrc : cchSrc; + size_t cchNext = cchPlain; + + if (pchDollar) + { + /* Treat lone $ w/o a following { as plain text. */ + if ( cchPlain + 1 >= cchSrc + && pchDollar[0] == '$' + && ( cchPlain + 1 == cchSrc + || pchDollar[1] != '{') ) + { + cchPlain += 1; + cchNext += 1; + pchDollar += 1; + } + /* Eat up escaped dollars: $$ -> $ */ + else + while (cchNext + 2 <= cchSrc && pchDollar[1] == '$' && pchDollar[0] == '$') + { + cchPlain += 1; + cchNext += 2; + pchDollar += 2; + } + } + + /* Finally copy out plain text.*/ + if (cchPlain > 0) + { + if (cchPlain >= cbRetAlloc - offRet) + { + size_t const cbNeeded = RT_ALIGN_Z(offRet + cchPlain + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64), 16); + void *pvNew = RTMemTmpAlloc(cbNeeded); + if (pvNew) + memcpy(pvNew, pszRet, offRet); + RTMemTmpFree(pszRet); + pszRet = (char *)pvNew; + if (pvNew) + cbRetAlloc = cbNeeded; + else + { + RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded); + return NULL; + } + } + + memcpy(&pszRet[offRet], pchSrc, cchPlain); + offRet += cchPlain; + pszRet[offRet] = '\0'; + pchSrc += cchNext; + cchSrc -= cchNext; + if (!cchSrc) + break; + + /* If we don't have ${, just loop. */ + if ( cchSrc < 2 + || pchSrc[0] != '$' + || pchSrc[1] != '{') + continue; + } + + /* + * If we get down here we have a ${ or $( at pchSrc. The fun part now is + * finding the end of it and recursively dealing with any sub-expansions first. + */ + Assert(pchSrc[0] == '$' && pchSrc[1] == '{'); + size_t cchVarRef; + bool fNested; + if (expr_expand_find_end(pThis, pchSrc, cchSrc, &cchVarRef, &fNested) == kExprRet_Ok) + { + /* Lookup the variable. Simple when it's a plain one, for nested ones we + first have to expand the variable name itself before looking it up. */ + char *pszValue; + int vrc; + if (!fNested) + vrc = pThis->pEvaluator->pfnQueryVariable(&pchSrc[2], cchSrc - 3, pThis->pEvaluator->pvUser, &pszValue); + else + { + char *pszName = expr_expand_string(pThis, &pchSrc[2], cchSrc - 3, cDepth + 1); + if (!pszName) + { + RTMemTmpFree(pszRet); + return NULL; + } + vrc = pThis->pEvaluator->pfnQueryVariable(pszName, strlen(pszName), pThis->pEvaluator->pvUser, &pszValue); + RTMemTmpFree(pszName); + } + + /* Treat variables that aren't found as empty strings for now. + This may need to become configurable later. */ + char *pszValueFree = pszValue; + static char s_szNotFound[] = ""; + if (vrc == VERR_NOT_FOUND) + { + pszValue = s_szNotFound; + vrc = VINF_SUCCESS; + } + + if (RT_SUCCESS(vrc)) + { + /* + * Append the value to the return string. + */ + size_t cchValue = strlen(pszValue); + if (cchValue > 0) + { + if (cchValue >= cbRetAlloc - offRet) + { + size_t const cbNeeded = RT_ALIGN_Z(offRet + cchValue + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64), + 16); + void *pvNew = RTMemTmpAlloc(cbNeeded); + if (pvNew) + memcpy(pvNew, pszRet, offRet); + RTMemTmpFree(pszRet); + pszRet = (char *)pvNew; + if (pvNew) + cbRetAlloc = cbNeeded; + else + { + RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded); + RTStrFree(pszValueFree); + return NULL; + } + } + + memcpy(&pszRet[offRet], pszValue, cchValue); + offRet += cchValue; + pszRet[offRet] = '\0'; + } + pchSrc += cchVarRef; + cchSrc -= cchVarRef; + RTStrFree(pszValueFree); + continue; + } + } + RTMemTmpFree(pszRet); + return NULL; + } + return pszRet; + } + RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbRetAlloc); + } + else + RTErrInfoSet(pThis->pErrInfo, VERR_TOO_MUCH_DATA, "Too deeply nested variable expression"); + return NULL; +} + + +/** + * Simplifies a string variable. + * + * @returns kExprRet_Ok or kExprRet_Error. + * @param pThis The evaluator expression instance. + * @param pVar The variable. + */ +static EXPRRET expr_var_make_simple_string(PEXPR pThis, PEXPRVAR pVar) +{ + switch (pVar->enmType) + { + case kExprVar_Num: + { + char *psz = (char *)RTMemTmpAlloc(EXPR_NUM_LEN); + if (psz) + { + expr_num_to_string(psz, pVar->uVal.i); + pVar->uVal.psz = psz; + pVar->enmType = kExprVar_SimpleString; + } + else + { + RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", EXPR_NUM_LEN); + return kExprRet_Error; + } + break; + } + + case kExprVar_String: + case kExprVar_QuotedString: + { + Assert(strchr(pVar->uVal.psz, '$')); + char *psz = expr_expand_string(pThis, pVar->uVal.psz, strlen(pVar->uVal.psz), 0); + if (psz) + { + RTMemTmpFree(pVar->uVal.psz); + pVar->uVal.psz = psz; + + pVar->enmType = pVar->enmType == kExprVar_String + ? kExprVar_SimpleString + : kExprVar_QuotedSimpleString; + } + else + return kExprRet_Error; + break; + } + + case kExprVar_SimpleString: + case kExprVar_QuotedSimpleString: + /* nothing to do. */ + break; + + default: + AssertMsgFailed(("%d\n", pVar->enmType)); + } + return kExprRet_Ok; +} + + +#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: + AssertMsgFailed(("%d\n", pVar->enmType)); + } +} +#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: + { + EXPRRET rc = expr_var_make_simple_string(pThis, pVar); + if (rc != kExprRet_Ok) + return rc; + RT_FALL_THROUGH(); + } + 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: + return expr_error(pThis, "Cannot convert a quoted string to a number"); + + default: + AssertMsgFailedReturn(("%d\n", pVar->enmType), kExprRet_Error); + } + + return kExprRet_Ok; +} + + +/** + * Try to turn the variable into a number. + * + * @returns status code. + * @param pThis The instance. + * @param pVar The variable. + */ +static EXPRRET expr_var_try_make_num(PEXPR pThis, PEXPRVAR pVar) +{ + EXPRRET rc; + switch (pVar->enmType) + { + case kExprVar_Num: + /* nothing to do. */ + break; + + case kExprVar_String: + rc = expr_var_make_simple_string(pThis, pVar); + if (rc != kExprRet_Ok) + return rc; + RT_FALL_THROUGH(); + case kExprVar_SimpleString: + { + EXPRINT64 i; + rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 1 /* fQuiet */); + if (rc < kExprRet_Ok) + return rc; + expr_var_assign_num(pVar, i); + break; + } + + case kExprVar_QuotedString: + case kExprVar_QuotedSimpleString: + /* can't do this */ + return kExprRet_Error; + + default: + AssertMsgFailedReturn(("%d\n", pVar->enmType), 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 pThis The instance. + * @param pVar The variable. + */ +static EXPRRET expr_var_make_bool(PEXPR pThis, PEXPRVAR pVar) +{ + EXPRRET rc = kExprRet_Ok; + + switch (pVar->enmType) + { + case kExprVar_Num: + pVar->uVal.i = !!pVar->uVal.i; + break; + + case kExprVar_String: + rc = expr_var_make_simple_string(pThis, pVar); + if (rc != kExprRet_Ok) + break; + RT_FALL_THROUGH(); + case kExprVar_SimpleString: + { + /* + * Try convert it to a number. If that fails, check for 'true' or + * 'false', if neither then use python / GNU make logic wrt strings. + */ + EXPRINT64 iVal; + char const *psz = pVar->uVal.psz; + while (RT_C_IS_BLANK(*psz)) + psz++; + if ( *psz + && expr_string_to_num(pThis, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok) + expr_var_assign_bool(pVar, iVal != 0); + else if ( strncmp(psz, RT_STR_TUPLE("true")) == 0 + && *RTStrStripL(&psz[sizeof("true") - 1]) == '\0') + expr_var_assign_bool(pVar, true); + else if ( strncmp(psz, RT_STR_TUPLE("false")) == 0 + && *RTStrStripL(&psz[sizeof("false") - 1]) == '\0') + expr_var_assign_bool(pVar, false); + else + expr_var_assign_bool(pVar, *psz != '\0'); + break; + } + + case kExprVar_QuotedString: + rc = expr_var_make_simple_string(pThis, pVar); + if (rc != kExprRet_Ok) + break; + RT_FALL_THROUGH(); + case kExprVar_QuotedSimpleString: + /* + * Use python / GNU make boolean logic: non-empty string means true. + * No stripping here, as the string is quoted as should be taken exactly as given. + */ + expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0'); + break; + + default: + AssertMsgFailed(("%d\n", pVar->enmType)); + } + + return rc; +} + + +/** + * 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. + * @param pszOp The operator requesting this (for errors). + */ +static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp) +{ +/** @todo Add flag for selecting preference here when forcing types */ + + + /* + * 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(pThis, pVar2); + else if (!expr_var_is_string(pVar2)) + expr_var_try_make_num(pThis, pVar1); + else + { + /* + * Both are strings, simplify them then see if both can be made into numbers. + */ + EXPRRET rc = expr_var_make_simple_string(pThis, pVar1); + if (rc == kExprRet_Ok) + rc = expr_var_make_simple_string(pThis, pVar2); + if (rc == kExprRet_Ok) + { + EXPRINT64 iVar1; + EXPRINT64 iVar2; + if ( expr_string_to_num(pThis, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok + && expr_string_to_num(pThis, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok) + { + expr_var_assign_num(pVar1, iVar1); + expr_var_assign_num(pVar2, iVar2); + } + } + else + return rc; + } + } + } + else + { + EXPRRET rc = expr_var_make_simple_string(pThis, pVar1); + if (rc == kExprRet_Ok) + rc = expr_var_make_simple_string(pThis, pVar2); + if (rc == kExprRet_Ok) + { /* likely */ } + else + return rc; + } + + /* + * Complain if they aren't the same type now. + */ + if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2)) + return expr_error(pThis, "Unable to unify types for \"%s\"", pszOp); + return kExprRet_Ok; +} + + + +/********************************************************************************************************************************* +* Operators * +*********************************************************************************************************************************/ + +/** + * Is variable defined, unary. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_defined(PEXPR pThis) +{ + PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; + + EXPRRET rc = expr_var_make_simple_string(pThis, pVar); + if (rc == kExprRet_Ok) + { + int vrc = pThis->pEvaluator->pfnQueryVariable(pVar->uVal.psz, strlen(pVar->uVal.psz), pThis->pEvaluator->pvUser, NULL); + expr_var_assign_bool(pVar, vrc != VERR_NOT_FOUND); + } + + return rc; +} + + +/** + * Does file(/dir/whatever) exist, unary. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_exists(PEXPR pThis) +{ + EXPRRET rc; + PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; + + if (pThis->pEvaluator->fFlags & RTEXPREVAL_F_EXISTS_OP) + { + rc = expr_var_make_simple_string(pThis, pVar); + if (rc == kExprRet_Ok) + expr_var_assign_bool(pVar, RTPathExists(pVar->uVal.psz) == 0); + } + else + rc = expr_error(pThis, "The 'exists' operator is not accessible"); + + return rc; +} + + +/** + * Convert to boolean. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_bool(PEXPR pThis) +{ + return expr_var_make_bool(pThis, &pThis->aVars[pThis->iVar]); +} + + +/** + * 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]; + EXPRRET rc = expr_var_make_simple_string(pThis, pVar); + if (rc == kExprRet_Ok) + expr_var_assign_num(pVar, strlen(pVar->uVal.psz)); + + return rc; +} + + +/** + * 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]; + EXPRRET rc = expr_var_make_simple_string(pThis, pVar); + if (rc == kExprRet_Ok) + pVar->enmType = kExprVar_QuotedSimpleString; + + return rc; +} + + +/** + * 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) +{ + PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; + EXPRRET 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) +{ + PEXPRVAR pVar = &pThis->aVars[pThis->iVar]; + EXPRRET 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]; + EXPRRET rc = expr_var_make_bool(pThis, pVar); + if (rc == kExprRet_Ok) + pVar->uVal.i = !pVar->uVal.i; + + return rc; +} + + +/** + * Multiplication. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_multiply(PEXPR pThis) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_num(pThis, pVar1); + if (rc >= kExprRet_Ok) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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, RTStrVersionCompare(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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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, RTStrVersionCompare(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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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, RTStrVersionCompare(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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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, RTStrVersionCompare(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) +{ + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + EXPRRET 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. */ + rc = expr_var_make_simple_string(pThis, pVar1); + if (rc == kExprRet_Ok) + rc = expr_var_make_simple_string(pThis, pVar2); + if (rc == kExprRet_Ok) + { + if (!RTStrVersionCompare(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(pThis, pVar1) >= kExprRet_Ok + && expr_var_try_make_num(pThis, 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(pThis, pVar1) >= kExprRet_Ok + && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok) + expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); + else + { + rc = expr_var_make_simple_string(pThis, pVar1); + if (rc == kExprRet_Ok) + rc = expr_var_make_simple_string(pThis, pVar2); + if (rc == kExprRet_Ok) + expr_var_assign_bool(pVar1, RTStrVersionCompare(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. */ + rc = expr_var_make_simple_string(pThis, pVar1); + if (rc == kExprRet_Ok) + rc = expr_var_make_simple_string(pThis, pVar2); + if (rc == kExprRet_Ok) + { + 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(pThis, pVar1) >= kExprRet_Ok + && expr_var_try_make_num(pThis, 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. + */ + /** @todo this needs to be redone, both because we're hiding alloc errors + * here but also because this should be controlled by a flag. */ + if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok + && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok) + expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i); + else + rc = expr_error(pThis, "Cannot compare strings and numbers"); + } + + 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 = 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 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 = 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 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 = 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; +} + + +/** + * Logical AND. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_logical_and(PEXPR pThis) +{ + bool fResult = false; + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_bool(pThis, pVar1); + if ( rc == kExprRet_Ok + && pVar1->uVal.i != 0) + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + rc = expr_var_make_bool(pThis, pVar2); + if (rc == kExprRet_Ok && pVar2->uVal.i != 0) + fResult = true; + } + expr_var_assign_bool(pVar1, fResult); + expr_pop_and_delete_var(pThis); + return rc; +} + + +/** + * Logical OR. + * + * @returns Status code. + * @param pThis The instance. + */ +static EXPRRET expr_op_logical_or(PEXPR pThis) +{ + bool fResult = false; + PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1]; + EXPRRET rc = expr_var_make_bool(pThis, pVar1); + if (rc == kExprRet_Ok) + { + if (pVar1->uVal.i) + fResult = true; + else + { + PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar]; + rc = expr_var_make_bool(pThis, pVar2); + if (rc == kExprRet_Ok && pVar2->uVal.i != 0) + fResult = true; + } + } + expr_var_assign_bool(pVar1, fResult); + expr_pop_and_delete_var(pThis); + return rc; +} + + +/** + * 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 + rc = expr_error(pThis, "Missing ')'"); + + 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) +{ + RT_NOREF_PV(pThis); + AssertFailed(); + 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("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. + */ + for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++) + { + unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0]; + if (!g_abOpStartCharMap[ch]) + { + g_abOpStartCharMap[ch] = (i << 2) | 1; + if (!RT_C_IS_ALPHA(ch)) + g_abOpStartCharMap[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_abOpStartCharMap[(unsigned char)(a_ch)] == 0); \ + g_abOpStartCharMap[(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_abOpStartCharMap for details. + * @param ch The character. + */ +DECLINLINE(unsigned char) expr_map_get(char ch) +{ + return g_abOpStartCharMap[(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) == (RT_C_IS_ALPHA(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) + return expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz); + psz += pOp->cchOp; + } + else + pOp = &g_ExprEndOfExpOp; + pThis->psz = psz; + } + + /* + * Push it. + */ + if (pThis->iOp >= EXPR_MAX_OPERATORS - 1) + return expr_error(pThis, "Operator stack overflow"); + 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 pThis 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') + return expr_error(pThis, "Unexpected end of expression"); + + /* + * 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 + rc = expr_error(pThis, "Operator stack overflow"); + 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++; + rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString); + if (ch != '\0') + psz++; + } + else if (ch == '\'') + { + pszStart = ++psz; + while ((ch = *psz) != '\0' && ch != '\'') + psz++; + rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, + kExprVar_QuotedSimpleString); + if (ch != '\0') + psz++; + } + else + { + unsigned cPars = 0; + pszStart = psz; + while ((ch = *psz) != '\0') + { + /* ${asdf} needs special handling. */ + if ( ch == '$' + && psz[1] == '{') + { + psz++; + if (cPars < EXPR_MAX_VAR_RECURSION) + ++cPars; + else + { + rc = expr_error(pThis, "Too deep nesting of variable expansions"); + break; + } + } + else if (ch == '}') + { + if (cPars > 0) + cPars--; + } + else if (cPars == 0) + { + 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) + rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String); + } + } + else + rc = expr_error(pThis, "Operand stack overflow"); + 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) + { + rc = expr_error(pThis, "Unexpected \"%s\"", pOp->szOp); + 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--; + } + RTMemTmpFree(pThis); +} + + +/** + * Instantiates an expression evaluator. + * + * @returns The instance. + */ +static PEXPR expr_create(RTEXPREVALINT *pThis, const char *pch, size_t cch, PRTERRINFO pErrInfo) +{ + cch = RTStrNLen(pch, cch); + + PEXPR pExpr = (PEXPR)RTMemTmpAllocZ(sizeof(*pExpr) + cch + 1); + if (pExpr) + { + pExpr->psz = pExpr->pszExpr = (char *)memcpy(pExpr + 1, pch, cch); + pExpr->pErrInfo = pErrInfo; + pExpr->pEvaluator = pThis; + pExpr->pPending = NULL; + pExpr->iVar = -1; + pExpr->iOp = -1; + + expr_map_init(); + } + return pExpr; +} + + + +/********************************************************************************************************************************* +* API * +*********************************************************************************************************************************/ + +/** @callback_method_impl{PFNRTEXPREVALQUERYVARIABLE, Stub} */ +static DECLCALLBACK(int) rtExprEvalDummyQueryVariable(const char *pchName, size_t cchName, void *pvUser, char **ppszValue) +{ + RT_NOREF(pchName, cchName, pvUser); + if (ppszValue) + *ppszValue = NULL; + return VERR_NOT_FOUND; +} + + +RTDECL(int) RTExprEvalCreate(PRTEXPREVAL phEval, uint64_t fFlags, const char *pszName, + void *pvUser, PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable) +{ + AssertPtrReturn(phEval, VERR_INVALID_POINTER); + *phEval = NULL; + AssertPtrNullReturn(pfnQueryVariable, VERR_INVALID_POINTER); + AssertPtrReturn(pszName, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~0), VERR_INVALID_FLAGS); + + char *pszNameCopy = RTStrDup(pszName); + if (pszNameCopy) + { + RTEXPREVALINT *pThis = (RTEXPREVALINT *)RTMemAllocZ(sizeof(*pThis)); + if (pThis) + { + pThis->u32Magic = RTEXPREVAL_MAGIC; + pThis->cRefs = 1; + pThis->fFlags = fFlags; + pThis->pszName = pszNameCopy; + pThis->pvUser = pvUser; + pThis->pfnQueryVariable = pfnQueryVariable ? pfnQueryVariable : rtExprEvalDummyQueryVariable; + *phEval = pThis; + return VINF_SUCCESS; + + } + + RTStrFree(pszNameCopy); + return VERR_NO_MEMORY; + } + return VERR_NO_STR_MEMORY; +} + + +RTDECL(uint32_t) RTExprEvalRetain(RTEXPREVAL hEval) +{ + RTEXPREVALINT *pThis = hEval; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX); + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + Assert(cRefs > 1); + Assert(cRefs < 512); + return cRefs; +} + + +RTDECL(uint32_t) RTExprEvalRelease(RTEXPREVAL hEval) +{ + RTEXPREVALINT *pThis = hEval; + AssertPtrReturn(pThis, UINT32_MAX); + AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX); + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + Assert(cRefs < 512); + if (cRefs == 0) + { + pThis->u32Magic = ~RTEXPREVAL_MAGIC; + if (pThis->pszName) + { + RTStrFree(pThis->pszName); + pThis->pszName = NULL; + } + RTMemFree(pThis); + return 0; + } + return cRefs; +} + + +RTDECL(int) RTExprEvalToBool(RTEXPREVAL hEval, const char *pch, size_t cch, bool *pfResult, PRTERRINFO pErrInfo) +{ + AssertPtrReturn(pfResult, VERR_INVALID_POINTER); + *pfResult = false; + RTEXPREVALINT *pThis = hEval; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE); + + /* + * Instantiate the expression evaluator and let it have a go at it. + */ + int rc; + PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo); + if (pExpr) + { + 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, &pExpr->aVars[0]) == kExprRet_Ok + && pExpr->aVars[0].uVal.i) + *pfResult = true; + rc = VINF_SUCCESS; + } + else + rc = VERR_PARSE_ERROR; /** @todo better errors? */ + expr_destroy(pExpr); + } + else + rc = VERR_NO_TMP_MEMORY; + return rc; +} + + +RTDECL(int) RTExprEvalToInteger(RTEXPREVAL hEval, const char *pch, size_t cch, int64_t *piResult, PRTERRINFO pErrInfo) +{ + AssertPtrReturn(piResult, VERR_INVALID_POINTER); + *piResult = INT64_MAX; + RTEXPREVALINT *pThis = hEval; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE); + + /* + * Instantiate the expression evaluator and let it have a go at it. + */ + int rc; + PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo); + if (pExpr) + { + if (expr_eval(pExpr) >= kExprRet_Ok) + { + /* + * Convert the result (on top of the stack) to boolean and + * set our return value accordingly. + */ + PEXPRVAR pVar = &pExpr->aVars[0]; + EXPRRET rcExpr = expr_var_make_num(pExpr, pVar); + if (rcExpr >= kExprRet_Ok) + { + *piResult = pVar->uVal.i; + rc = VINF_SUCCESS; + } + else + rc = VERR_PARSE_ERROR; /** @todo better error! */ + } + else + rc = VERR_PARSE_ERROR; /** @todo better errors? */ + expr_destroy(pExpr); + } + else + rc = VERR_NO_TMP_MEMORY; + return rc; +} + + +RTDECL(int) RTExprEvalToString(RTEXPREVAL hEval, const char *pch, size_t cch, char **ppszResult, PRTERRINFO pErrInfo) +{ + AssertPtrReturn(ppszResult, VERR_INVALID_POINTER); + *ppszResult = NULL; + RTEXPREVALINT *pThis = hEval; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE); + + /* + * Instantiate the expression evaluator and let it have a go at it. + */ + int rc; + PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo); + if (pExpr) + { + 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]; + if (expr_var_make_simple_string(pExpr, pVar) == kExprRet_Ok) + rc = RTStrDupEx(ppszResult, pVar->uVal.psz); + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_PARSE_ERROR; + expr_destroy(pExpr); + } + else + rc = VERR_NO_TMP_MEMORY; + + return rc; +} + -- cgit v1.2.3