Index: trunk/src/VBox/Runtime/common/misc/expreval.cpp
===================================================================
--- trunk/src/VBox/Runtime/common/misc/expreval.cpp	(revision 93170)
+++ trunk/src/VBox/Runtime/common/misc/expreval.cpp	(revision 93170)
@@ -0,0 +1,2743 @@
+/* $Id$ */
+/** @file
+ * expreval - Expressions evaluator.
+ */
+
+/*
+ * Copyright (C) 2022 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE 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.
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/expreval.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+*   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   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 (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 expansoins
+ * 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.
+ *
+ * @note    This code would be somewhat simpler if we only used one style of
+ *          wrappers (either parentheses or curly brackets).
+ */
+DECL_NO_INLINE(static, EXPRRET) expr_expand_find_end(PEXPR pThis, const char *pchSrc, size_t cchSrc,
+                                                     size_t *pcchVarRef, bool *pfNested)
+{
+    const char * const  pchStart = pchSrc;
+    char                achPars[EXPR_MAX_VAR_RECURSION];
+
+    /*
+     * Push the initial expression.
+     */
+    Assert(cchSrc >= 2);
+    Assert(pchSrc[0] == '$');
+    Assert(pchSrc[1] == '(' || pchSrc[1] == '}');
+    unsigned            cPars    = 1;
+    char                chEndPar = pchSrc[1] == '(' ? ')' : '}';
+    achPars[0] = chEndPar;
+    pchSrc += 2;
+    cchSrc -= 2;
+
+    /*
+     * Parse the rest of the string till we've back at cPars == 0.
+     */
+    *pfNested = false;
+    while (cchSrc > 0)
+    {
+        char       ch2;
+        char const ch = *pchSrc;
+        if (   ch == '$'
+            && cchSrc >= 2
+            && (   (ch2 = pchSrc[1]) == '('
+                || ch2 == '{'))
+        {
+            if (cPars < RT_ELEMENTS(achPars))
+                achPars[cPars++] = chEndPar = ch2 == '(' ? ')' : '}';
+            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 == chEndPar)
+            {
+                if (--cPars == 0)
+                {
+                    *pcchVarRef = pchSrc - pchStart;
+                    return kExprRet_Ok;
+                }
+                chEndPar = achPars[cPars];
+            }
+            Assert(chEndPar != '\0');
+        }
+    }
+    *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.
+ *
+ * @note    This code would be somewhat simpler if we only used one style of
+ *          wrappers (either parentheses or curly brackets).
+ */
+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 ( or { as plain text. */
+                    if (   cchPlain + 1 >= cchSrc
+                        && pchDollar[0] == '$'
+                        && (   cchPlain + 1 == cchSrc
+                            || (pchDollar[1] != '{' && 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 ${ or $( loop, just loop. */
+                    if (   cchSrc < 2
+                        || pchSrc[0] != '$'
+                        || (pchSrc[1] != '{' && 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] == '{' || 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.
+ */
+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   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')
+        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
+        {
+            char    achPars[EXPR_MAX_VAR_RECURSION];
+            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])))
+                    {
+                        rc = expr_error(pThis, "Too deep nesting of variable expansions");
+                        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)
+                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{FNRTEXPREVALQUERYVARIABLE, 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;
+
+        }
+        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;
+        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;
+}
+
