#ifdef CONFIG_WITH_IF_CONDITIONALS
/* $Id: expreval.c 3544 2022-01-29 02:22:03Z bird $ */
/** @file
 * expreval - Expressions evaluator, C / BSD make / nmake style.
 */

/*
 * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
 *
 * This file is part of kBuild.
 *
 * kBuild is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * kBuild is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with kBuild.  If not, see <http://www.gnu.org/licenses/>
 *
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include "makeint.h"
#include <assert.h>

#include <glob.h>

#include "filedef.h"
#include "dep.h"
#include "job.h"
#include "commands.h"
#include "variable.h"
#include "rule.h"
#include "debug.h"
#include "hash.h"
#include "version_compare.h"
#include <ctype.h>
#ifndef _MSC_VER
# include <stdint.h>
#endif
#include <stdarg.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

/** Check if @a a_ch is a valid separator for a alphabetical binary
 *  operator, omitting isspace. */
#define EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch) \
    (ispunct((a_ch)) && (a_ch) != '@' && (a_ch) != '_')

/** Check if @a a_ch is a valid separator for a alphabetical binary operator. */
#define EXPR_IS_OP_SEPARATOR(a_ch) \
    (isspace((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch))


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/** The 64-bit signed integer type we're using. */
#ifdef _MSC_VER
typedef __int64 EXPRINT64;
#else
# include <stdint.h>
typedef int64_t EXPRINT64;
#endif

/** Pointer to a evaluator instance. */
typedef struct EXPR *PEXPR;


/**
 * Operand variable type.
 */
typedef enum
{
    /** Invalid zero entry. */
    kExprVar_Invalid = 0,
    /** A number. */
    kExprVar_Num,
    /** A string in need of expanding (perhaps). */
    kExprVar_String,
    /** A simple string that doesn't need expanding. */
    kExprVar_SimpleString,
    /** A quoted string in need of expanding (perhaps). */
    kExprVar_QuotedString,
    /** A simple quoted string that doesn't need expanding. */
    kExprVar_QuotedSimpleString,
    /** The end of the valid variable types. */
    kExprVar_End
} EXPRVARTYPE;

/**
 * Operand variable.
 */
typedef struct
{
    /** The variable type. */
    EXPRVARTYPE enmType;
    /** The variable. */
    union
    {
        /** Pointer to the string. */
        char *psz;
        /** The variable. */
        EXPRINT64 i;
    } uVal;
} EXPRVAR;
/** Pointer to a operand variable. */
typedef EXPRVAR *PEXPRVAR;
/** Pointer to a const operand variable. */
typedef EXPRVAR const *PCEXPRVAR;

/**
 * Operator return statuses.
 */
typedef enum
{
    kExprRet_Error = -1,
    kExprRet_Ok = 0,
    kExprRet_Operator,
    kExprRet_Operand,
    kExprRet_EndOfExpr,
    kExprRet_End
} EXPRRET;

/**
 * Operator.
 */
typedef struct
{
    /** The operator. */
    char szOp[11];
    /** The length of the operator string. */
    char cchOp;
    /** The pair operator.
     * This is used with '(' and '?'. */
    char chPair;
    /** The precedence. Higher means higher. */
    char iPrecedence;
    /** The number of arguments it takes. */
    signed char cArgs;
    /** Pointer to the method implementing the operator. */
    EXPRRET (*pfn)(PEXPR pThis);
} EXPROP;
/** Pointer to a const operator. */
typedef EXPROP const *PCEXPROP;

/**
 * Expression evaluator instance.
 */
typedef struct EXPR
{
    /** The full expression. */
    const char *pszExpr;
    /** The current location. */
    const char *psz;
    /** The current file location, used for errors. */
    const floc *pFileLoc;
    /** Pending binary operator. */
    PCEXPROP pPending;
    /** Top of the operator stack. */
    int iOp;
    /** Top of the operand stack. */
    int iVar;
    /** The operator stack. */
    PCEXPROP apOps[EXPR_MAX_OPERATORS];
    /** The operand stack. */
    EXPRVAR aVars[EXPR_MAX_OPERANDS];
} EXPR;


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
/** Operator start character map.
 * This indicates which characters that are starting operators and which aren't.
 *
 * Bit 0: Indicates that this char is used in operators.
 * Bit 1: When bit 0 is clear, this indicates whitespace.
 *        When bit 1 is set, this indicates whether the operator can be used
 *        immediately next to an operand without any clear separation.
 * Bits 2 thru 7: Index into g_aExprOps of the first operator starting with
 *        this character.
 */
static unsigned char g_auchOpStartCharMap[256];
/** Whether we've initialized the map. */
static int g_fExprInitializedMap = 0;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static void expr_unget_op(PEXPR pThis);
static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis);




/**
 * Displays an error message.
 *
 * The total string length must not exceed 256 bytes.
 *
 * @param   pThis       The evaluator instance.
 * @param   pszError    The message format string.
 * @param   ...         The message format args.
 */
static void expr_error(PEXPR pThis, const char *pszError, ...)
{
    char szTmp[256];
    va_list va;

    va_start(va, pszError);
    vsprintf(szTmp, pszError, va);
    va_end(va);

    OS(fatal,pThis->pFileLoc, "%s", szTmp);
}


/**
 * Converts a number to a string.
 *
 * @returns pszDst.
 * @param   pszDst  The string buffer to write into. Assumes length of EXPR_NUM_LEN.
 * @param   iSrc    The number to convert.
 */
static char *expr_num_to_string(char *pszDst, EXPRINT64 iSrc)
{
    static const char s_szDigits[17] = "0123456789abcdef";
    char szTmp[EXPR_NUM_LEN];
    char *psz = &szTmp[EXPR_NUM_LEN - 1];
    int fNegative;

    fNegative = iSrc < 0;
    if (fNegative)
    {
        /** @todo this isn't right for INT64_MIN. */
        iSrc = -iSrc;
    }

    *psz = '\0';
    do
    {
#if 0
        *--psz = s_szDigits[iSrc & 0xf];
        iSrc >>= 4;
#else
        *--psz = s_szDigits[iSrc % 10];
        iSrc /= 10;
#endif
    } while (iSrc);

#if 0
    *--psz = 'x';
    *--psz = '0';
#endif

    if (fNegative)
      *--psz = '-';

    /* copy it into the output buffer. */
    return (char *)memcpy(pszDst, psz, &szTmp[EXPR_NUM_LEN] - psz);
}


/**
 * Attempts to convert a (simple) string into a number.
 *
 * @returns status code.
 * @param   pThis   The evaluator instance. This is optional when fQuiet is true.
 * @param   piSrc   Where to store the numeric value on success.
 * @param   pszSrc  The string to try convert.
 * @param   fQuiet  Whether we should be quiet or grumpy on failure.
 */
static EXPRRET expr_string_to_num(PEXPR pThis, EXPRINT64 *piDst, const char *pszSrc, int fQuiet)
{
    EXPRRET rc = kExprRet_Ok;
    char const *psz = pszSrc;
    EXPRINT64 i;
    unsigned uBase;
    int fNegative;


    /*
     * Skip blanks.
     */
    while (ISBLANK(*psz))
        psz++;

    /*
     * Check for '-'.
     *
     * At this point we will not need to deal with operators, this is
     * just an indicator of negative numbers. If some operator ends up
     * here it's because it came from a string expansion and thus shall
     * not be interpreted. If this turns out to be an stupid restriction
     * it can be fixed, but for now it stays like this.
     */
    fNegative = *psz == '-';
    if (fNegative)
        psz++;

    /*
     * Determin base.
     *
     * Recognize some exsotic prefixes here in addition to the two standard ones.
     */
    if (*psz != '0')
        uBase = 10;
    else if (psz[1] == 'x' || psz[1] == 'X')
    {
        uBase = 16;
        psz += 2;
    }
    else if (psz[1] == 'b' || psz[1] == 'B')
    {
        uBase = 2;
        psz += 2;
    }
    else if (psz[1] == 'd' || psz[1] == 'D')
    {
        uBase = 10;
        psz += 2;
    }
    else if (psz[1] == 'o' || psz[1] == 'O')
    {
        uBase = 8;
        psz += 2;
    }
    else if (isdigit(psz[1]) && psz[1] != '9' && psz[1] != '8')
    {
        uBase = 8;
        psz++;
    }
    else
        uBase = 10;

    /*
     * Convert until we hit a non-digit.
     */
    i = 0;
    for (;;)
    {
        unsigned iDigit;
        int ch = *psz;
        switch (ch)
        {
            case '0':   iDigit =  0; break;
            case '1':   iDigit =  1; break;
            case '2':   iDigit =  2; break;
            case '3':   iDigit =  3; break;
            case '4':   iDigit =  4; break;
            case '5':   iDigit =  5; break;
            case '6':   iDigit =  6; break;
            case '7':   iDigit =  7; break;
            case '8':   iDigit =  8; break;
            case '9':   iDigit =  9; break;
            case 'a':
            case 'A':   iDigit = 10; break;
            case 'b':
            case 'B':   iDigit = 11; break;
            case 'c':
            case 'C':   iDigit = 12; break;
            case 'd':
            case 'D':   iDigit = 13; break;
            case 'e':
            case 'E':   iDigit = 14; break;
            case 'f':
            case 'F':   iDigit = 15; break;

            default:
                /* is the rest white space? */
                while (ISSPACE(*psz))
                    psz++;
                if (*psz != '\0')
                {
                    iDigit = uBase;
                    break;
                }
                /* fall thru */

            case '\0':
                if (fNegative)
                    i = -i;
                *piDst = i;
                return rc;
        }
        if (iDigit >= uBase)
        {
            if (fNegative)
                i = -i;
            *piDst = i;
            if (!fQuiet)
                expr_error(pThis, "Invalid number \"%.80s\"", pszSrc);
            return kExprRet_Error;
        }

        /* add the digit and advance */
        i *= uBase;
        i += iDigit;
        psz++;
    }
    /* not reached */
}


/**
 * Checks if the variable is a string or not.
 *
 * @returns 1 if it's a string, 0 otherwise.
 * @param   pVar    The variable.
 */
static int expr_var_is_string(PCEXPRVAR pVar)
{
    return pVar->enmType >= kExprVar_String;
}


/**
 * Checks if the variable contains a string that was quoted
 * in the expression.
 *
 * @returns 1 if if was a quoted string, otherwise 0.
 * @param   pVar    The variable.
 */
static int expr_var_was_quoted(PCEXPRVAR pVar)
{
    return pVar->enmType >= kExprVar_QuotedString;
}


/**
 * Deletes a variable.
 *
 * @param   pVar    The variable.
 */
static void expr_var_delete(PEXPRVAR pVar)
{
    if (expr_var_is_string(pVar))
    {
        free(pVar->uVal.psz);
        pVar->uVal.psz = NULL;
    }
    pVar->enmType = kExprVar_Invalid;
}


/**
 * Initializes a new variables with a sub-string value.
 *
 * @param   pVar    The new variable.
 * @param   psz     The start of the string value.
 * @param   cch     The number of chars to copy.
 * @param   enmType The string type.
 */
static void expr_var_init_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
{
    /* convert string needing expanding into simple ones if possible.  */
    if (    enmType == kExprVar_String
        &&  !memchr(psz, '$', cch))
        enmType = kExprVar_SimpleString;
    else if (   enmType == kExprVar_QuotedString
             && !memchr(psz, '$', cch))
        enmType = kExprVar_QuotedSimpleString;

    pVar->enmType = enmType;
    pVar->uVal.psz = xmalloc(cch + 1);
    memcpy(pVar->uVal.psz, psz, cch);
    pVar->uVal.psz[cch] = '\0';
}


#if 0  /* unused */
/**
 * Initializes a new variables with a string value.
 *
 * @param   pVar    The new variable.
 * @param   psz     The string value.
 * @param   enmType The string type.
 */
static void expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
{
    expr_var_init_substring(pVar, psz, strlen(psz), enmType);
}


/**
 * Assigns a sub-string value to a variable.
 *
 * @param   pVar    The new variable.
 * @param   psz     The start of the string value.
 * @param   cch     The number of chars to copy.
 * @param   enmType The string type.
 */
static void expr_var_assign_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
{
    expr_var_delete(pVar);
    expr_var_init_substring(pVar, psz, cch, enmType);
}


/**
 * Assignes a string value to a variable.
 *
 * @param   pVar    The variable.
 * @param   psz     The string value.
 * @param   enmType The string type.
 */
static void expr_var_assign_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
{
    expr_var_delete(pVar);
    expr_var_init_string(pVar, psz, enmType);
}
#endif /* unused */


/**
 * Simplifies a string variable.
 *
 * @param   pVar    The variable.
 */
static void expr_var_make_simple_string(PEXPRVAR pVar)
{
    switch (pVar->enmType)
    {
        case kExprVar_Num:
        {
            char *psz = (char *)xmalloc(EXPR_NUM_LEN);
            expr_num_to_string(psz, pVar->uVal.i);
            pVar->uVal.psz = psz;
            pVar->enmType = kExprVar_SimpleString;
            break;
        }

        case kExprVar_String:
        case kExprVar_QuotedString:
        {
            char *psz;
            assert(strchr(pVar->uVal.psz, '$'));

            psz = allocated_variable_expand(pVar->uVal.psz);
            free(pVar->uVal.psz);
            pVar->uVal.psz = psz;

            pVar->enmType = pVar->enmType == kExprVar_String
                          ? kExprVar_SimpleString
                          : kExprVar_QuotedSimpleString;
            break;
        }

        case kExprVar_SimpleString:
        case kExprVar_QuotedSimpleString:
            /* nothing to do. */
            break;

        default:
            assert(0);
    }
}


#if 0 /* unused */
/**
 * Turns a variable into a string value.
 *
 * @param   pVar    The variable.
 */
static void expr_var_make_string(PEXPRVAR pVar)
{
    switch (pVar->enmType)
    {
        case kExprVar_Num:
            expr_var_make_simple_string(pVar);
            break;

        case kExprVar_String:
        case kExprVar_SimpleString:
        case kExprVar_QuotedString:
        case kExprVar_QuotedSimpleString:
            /* nothing to do. */
            break;

        default:
            assert(0);
    }
}
#endif /* unused */


/**
 * Initializes a new variables with a integer value.
 *
 * @param   pVar    The new variable.
 * @param   i       The integer value.
 */
static void expr_var_init_num(PEXPRVAR pVar, EXPRINT64 i)
{
    pVar->enmType = kExprVar_Num;
    pVar->uVal.i = i;
}


/**
 * Assigns a integer value to a variable.
 *
 * @param   pVar    The variable.
 * @param   i       The integer value.
 */
static void expr_var_assign_num(PEXPRVAR pVar, EXPRINT64 i)
{
    expr_var_delete(pVar);
    expr_var_init_num(pVar, i);
}


/**
 * Turns the variable into a number.
 *
 * @returns status code.
 * @param   pThis   The evaluator instance.
 * @param   pVar    The variable.
 */
static EXPRRET expr_var_make_num(PEXPR pThis, PEXPRVAR pVar)
{
    switch (pVar->enmType)
    {
        case kExprVar_Num:
            /* nothing to do. */
            break;

        case kExprVar_String:
            expr_var_make_simple_string(pVar);
            /* fall thru */
        case kExprVar_SimpleString:
        {
            EXPRINT64 i;
            EXPRRET rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 0 /* fQuiet */);
            if (rc < kExprRet_Ok)
                return rc;
            expr_var_assign_num(pVar, i);
            break;
        }

        case kExprVar_QuotedString:
        case kExprVar_QuotedSimpleString:
            expr_error(pThis, "Cannot convert a quoted string to a number");
            return kExprRet_Error;

        default:
            assert(0);
            return kExprRet_Error;
    }

    return kExprRet_Ok;
}


/**
 * Try to turn the variable into a number.
 *
 * @returns status code.
 * @param   pVar    The variable.
 */
static EXPRRET expr_var_try_make_num(PEXPRVAR pVar)
{
    switch (pVar->enmType)
    {
        case kExprVar_Num:
            /* nothing to do. */
            break;

        case kExprVar_String:
            expr_var_make_simple_string(pVar);
            /* fall thru */
        case kExprVar_SimpleString:
        {
            EXPRINT64 i;
            EXPRRET rc = expr_string_to_num(NULL, &i, pVar->uVal.psz, 1 /* fQuiet */);
            if (rc < kExprRet_Ok)
                return rc;
            expr_var_assign_num(pVar, i);
            break;
        }

        default:
            assert(0);
        case kExprVar_QuotedString:
        case kExprVar_QuotedSimpleString:
            /* can't do this */
            return kExprRet_Error;
    }

    return kExprRet_Ok;
}


/**
 * Initializes a new variables with a boolean value.
 *
 * @param   pVar    The new variable.
 * @param   f       The boolean value.
 */
static void expr_var_init_bool(PEXPRVAR pVar, int f)
{
    pVar->enmType = kExprVar_Num;
    pVar->uVal.i = !!f;
}


/**
 * Assigns a boolean value to a variable.
 *
 * @param   pVar    The variable.
 * @param   f       The boolean value.
 */
static void expr_var_assign_bool(PEXPRVAR pVar, int f)
{
    expr_var_delete(pVar);
    expr_var_init_bool(pVar, f);
}


/**
 * Turns the variable into an boolean.
 *
 * @returns the boolean interpretation.
 * @param   pVar    The variable.
 */
static int expr_var_make_bool(PEXPRVAR pVar)
{
    switch (pVar->enmType)
    {
        case kExprVar_Num:
            pVar->uVal.i = !!pVar->uVal.i;
            break;

        case kExprVar_String:
            expr_var_make_simple_string(pVar);
            /* fall thru */
        case kExprVar_SimpleString:
        {
            /*
             * Try convert it to a number. If that fails, use the
             * GNU make boolean logic - not empty string means true.
             */
            EXPRINT64 iVal;
            char const *psz = pVar->uVal.psz;
            while (ISBLANK(*psz))
                psz++;
            if (    *psz
                &&  expr_string_to_num(NULL, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok)
                expr_var_assign_bool(pVar, iVal != 0);
            else
                expr_var_assign_bool(pVar, *psz != '\0');
            break;
        }

        case kExprVar_QuotedString:
            expr_var_make_simple_string(pVar);
            /* fall thru */
        case kExprVar_QuotedSimpleString:
            /*
             * Use GNU make boolean logic (not empty string means true).
             * No stripping here, the string is quoted.
             */
            expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0');
            break;

        default:
            assert(0);
            break;
    }

    return pVar->uVal.i;
}


/**
 * Pops a varable off the stack and deletes it.
 * @param   pThis   The evaluator instance.
 */
static void expr_pop_and_delete_var(PEXPR pThis)
{
    expr_var_delete(&pThis->aVars[pThis->iVar]);
    pThis->iVar--;
}



/**
 * Tries to make the variables the same type.
 *
 * This will not convert numbers to strings, unless one of them
 * is a quoted string.
 *
 * this will try convert both to numbers if neither is quoted. Both
 * conversions will have to suceed for this to be commited.
 *
 * All strings will be simplified.
 *
 * @returns status code. Done complaining on failure.
 *
 * @param   pThis   The evaluator instance.
 * @param   pVar1   The first variable.
 * @param   pVar2   The second variable.
 */
static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp)
{
    /*
     * Try make the variables the same type before comparing.
     */
    if (    !expr_var_was_quoted(pVar1)
        &&  !expr_var_was_quoted(pVar2))
    {
        if (    expr_var_is_string(pVar1)
            ||  expr_var_is_string(pVar2))
        {
            if (!expr_var_is_string(pVar1))
                expr_var_try_make_num(pVar2);
            else if (!expr_var_is_string(pVar2))
                expr_var_try_make_num(pVar1);
            else
            {
                /*
                 * Both are strings, simplify them then see if both can be made into numbers.
                 */
                EXPRINT64 iVar1;
                EXPRINT64 iVar2;

                expr_var_make_simple_string(pVar1);
                expr_var_make_simple_string(pVar2);

                if (    expr_string_to_num(NULL, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok
                    &&  expr_string_to_num(NULL, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok)
                {
                    expr_var_assign_num(pVar1, iVar1);
                    expr_var_assign_num(pVar2, iVar2);
                }
            }
        }
    }
    else
    {
        expr_var_make_simple_string(pVar1);
        expr_var_make_simple_string(pVar2);
    }

    /*
     * Complain if they aren't the same type now.
     */
    if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2))
    {
        expr_error(pThis, "Unable to unify types for \"%s\"", pszOp);
        return kExprRet_Error;
    }
    return kExprRet_Ok;
}


/**
 * Is variable defined, unary.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_defined(PEXPR pThis)
{
    PEXPRVAR            pVar = &pThis->aVars[pThis->iVar];
    struct variable    *pMakeVar;

    expr_var_make_simple_string(pVar);
    pMakeVar = lookup_variable(pVar->uVal.psz, strlen(pVar->uVal.psz));
    expr_var_assign_bool(pVar, pMakeVar && *pMakeVar->value != '\0');

    return kExprRet_Ok;
}


/**
 * Does file(/dir/whatever) exist, unary.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_exists(PEXPR pThis)
{
    PEXPRVAR            pVar = &pThis->aVars[pThis->iVar];
    struct stat         st;

    expr_var_make_simple_string(pVar);
    expr_var_assign_bool(pVar, stat(pVar->uVal.psz, &st) == 0);

    return kExprRet_Ok;
}


/**
 * Is target defined, unary.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_target(PEXPR pThis)
{
    PEXPRVAR            pVar = &pThis->aVars[pThis->iVar];
    struct file        *pFile = NULL;

    /*
     * Because of secondary target expansion, lookup the unexpanded
     * name first.
     */
#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
    if (    pVar->enmType == kExprVar_String
        ||  pVar->enmType == kExprVar_QuotedString)
    {
        pFile = lookup_file(pVar->uVal.psz);
        if (    pFile
            &&  !pFile->need_2nd_target_expansion)
            pFile = NULL;
    }
    if (!pFile)
#endif
    {
        expr_var_make_simple_string(pVar);
        pFile = lookup_file(pVar->uVal.psz);
    }

    /*
     * Always inspect the head of a multiple target rule
     * and look for a file with commands.
     */
#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
    if (pFile && pFile->multi_head)
        pFile = pFile->multi_head;
#endif

    while (pFile && !pFile->cmds)
        pFile = pFile->prev;

    expr_var_assign_bool(pVar, pFile != NULL && pFile->is_target);

    return kExprRet_Ok;
}


/**
 * Convert to boolean.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_bool(PEXPR pThis)
{
    expr_var_make_bool(&pThis->aVars[pThis->iVar]);
    return kExprRet_Ok;
}


/**
 * Convert to number, works on quoted strings too.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_num(PEXPR pThis)
{
    PEXPRVAR pVar = &pThis->aVars[pThis->iVar];

    /* unquote the string */
    if (pVar->enmType == kExprVar_QuotedSimpleString)
        pVar->enmType = kExprVar_SimpleString;
    else if (pVar->enmType == kExprVar_QuotedString)
        pVar->enmType = kExprVar_String;

    return expr_var_make_num(pThis, pVar);
}


/**
 * Performs a strlen() on the simplified/converted string argument.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_strlen(PEXPR pThis)
{
    PEXPRVAR pVar = &pThis->aVars[pThis->iVar];

    expr_var_make_simple_string(pVar);
    expr_var_assign_num(pVar, strlen(pVar->uVal.psz));

    return kExprRet_Ok;
}


/**
 * Convert to string (simplified and quoted)
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_str(PEXPR pThis)
{
    PEXPRVAR pVar = &pThis->aVars[pThis->iVar];

    expr_var_make_simple_string(pVar);
    pVar->enmType = kExprVar_QuotedSimpleString;

    return kExprRet_Ok;
}


/**
 * Pluss (dummy / make_integer)
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_pluss(PEXPR pThis)
{
    return expr_var_make_num(pThis, &pThis->aVars[pThis->iVar]);
}


/**
 * Minus (negate)
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_minus(PEXPR pThis)
{
    EXPRRET     rc;
    PEXPRVAR    pVar = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar);
    if (rc >= kExprRet_Ok)
        pVar->uVal.i = -pVar->uVal.i;

    return rc;
}



/**
 * Bitwise NOT.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_bitwise_not(PEXPR pThis)
{
    EXPRRET     rc;
    PEXPRVAR    pVar = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar);
    if (rc >= kExprRet_Ok)
        pVar->uVal.i = ~pVar->uVal.i;

    return rc;
}


/**
 * Logical NOT.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_logical_not(PEXPR pThis)
{
    PEXPRVAR    pVar = &pThis->aVars[pThis->iVar];

    expr_var_make_bool(pVar);
    pVar->uVal.i = !pVar->uVal.i;

    return kExprRet_Ok;
}


/**
 * Multiplication.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_multiply(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i *= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}



/**
 * Division.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_divide(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i /= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}



/**
 * Modulus.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_modulus(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i %= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}



/**
 * Addition (numeric).
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_add(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i += pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Subtract (numeric).
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_sub(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i -= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}

/**
 * Bitwise left shift.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_shift_left(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i <<= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Bitwise right shift.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_shift_right(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i >>= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Less than or equal, version string.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_less_or_equal_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "vle");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Less than or equal.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_less_or_equal_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "<=");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Less than, version string.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_less_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "vlt");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Less than.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_less_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "<");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Greater or equal than, version string.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_greater_or_equal_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "vge");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Greater or equal than.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_greater_or_equal_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, ">=");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Greater than, version string.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_greater_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, "vgt");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Greater than.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_greater_than(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    rc = expr_var_unify_types(pThis, pVar1, pVar2, ">");
    if (rc >= kExprRet_Ok)
    {
        if (!expr_var_is_string(pVar1))
            expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
        else
            expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Equal, version strings.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_equal(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];
    int const   fIsString1 = expr_var_is_string(pVar1);

    /*
     * The same type?
     */
    if (fIsString1 == expr_var_is_string(pVar2))
    {
        if (!fIsString1)
            /* numbers are simple */
            expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
        else
        {
            /* try a normal string compare. */
            expr_var_make_simple_string(pVar1);
            expr_var_make_simple_string(pVar2);
            if (!version_compare(pVar1->uVal.psz, pVar2->uVal.psz))
                expr_var_assign_bool(pVar1, 1);
            /* try convert and compare as number instead. */
            else if (   expr_var_try_make_num(pVar1) >= kExprRet_Ok
                     && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
                expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
            /* ok, they really aren't equal. */
            else
                expr_var_assign_bool(pVar1, 0);
        }
    }
    else
    {
        /*
         * If the type differs, there are now two options:
         *  1. Try convert the string to a valid number and compare the numbers.
         *  2. Convert the non-string to a number and compare the strings.
         */
        if (   expr_var_try_make_num(pVar1) >= kExprRet_Ok
            && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
            expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
        else
        {
            expr_var_make_simple_string(pVar1);
            expr_var_make_simple_string(pVar2);
            expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) == 0);
        }
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Not equal, version string.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_ver_not_equal(PEXPR pThis)
{
    EXPRRET rc = expr_op_ver_equal(pThis);
    if (rc >= kExprRet_Ok)
        rc = expr_op_logical_not(pThis);
    return rc;
}


/**
 * Equal.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_equal(PEXPR pThis)
{
    EXPRRET     rc = kExprRet_Ok;
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];
    int const   fIsString1 = expr_var_is_string(pVar1);

    /*
     * The same type?
     */
    if (fIsString1 == expr_var_is_string(pVar2))
    {
        if (!fIsString1)
            /* numbers are simple */
            expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
        else
        {
            /* try a normal string compare. */
            expr_var_make_simple_string(pVar1);
            expr_var_make_simple_string(pVar2);
            if (!strcmp(pVar1->uVal.psz, pVar2->uVal.psz))
                expr_var_assign_bool(pVar1, 1);
            /* try convert and compare as number instead. */
            else if (   expr_var_try_make_num(pVar1) >= kExprRet_Ok
                     && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
                expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
            /* ok, they really aren't equal. */
            else
                expr_var_assign_bool(pVar1, 0);
        }
    }
    else
    {
        /*
         * If the type differs, there are now two options:
         *  1. Convert the string to a valid number and compare the numbers.
         *  2. Convert an empty string to a 'false' boolean value and compare
         *     numerically. This one is a bit questionable, so we don't try this.
         */
        if (   expr_var_try_make_num(pVar1) >= kExprRet_Ok
            && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
            expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
        else
        {
            expr_error(pThis, "Cannot compare strings and numbers");
            rc = kExprRet_Error;
        }
    }

    expr_pop_and_delete_var(pThis);
    return rc;
}


/**
 * Not equal.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_not_equal(PEXPR pThis)
{
    EXPRRET rc = expr_op_equal(pThis);
    if (rc >= kExprRet_Ok)
        rc = expr_op_logical_not(pThis);
    return rc;
}


/**
 * Bitwise AND.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_bitwise_and(PEXPR pThis)
{
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];
    EXPRRET     rc;

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i &= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return kExprRet_Ok;
}


/**
 * Bitwise XOR.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_bitwise_xor(PEXPR pThis)
{
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];
    EXPRRET     rc;

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i ^= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return kExprRet_Ok;
}


/**
 * Bitwise OR.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_bitwise_or(PEXPR pThis)
{
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];
    EXPRRET     rc;

    rc = expr_var_make_num(pThis, pVar1);
    if (rc >= kExprRet_Ok)
    {
        rc = expr_var_make_num(pThis, pVar2);
        if (rc >= kExprRet_Ok)
            pVar1->uVal.i |= pVar2->uVal.i;
    }

    expr_pop_and_delete_var(pThis);
    return kExprRet_Ok;
}


/**
 * Logical AND.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_logical_and(PEXPR pThis)
{
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    if (   expr_var_make_bool(pVar1)
        && expr_var_make_bool(pVar2))
        expr_var_assign_bool(pVar1, 1);
    else
        expr_var_assign_bool(pVar1, 0);

    expr_pop_and_delete_var(pThis);
    return kExprRet_Ok;
}


/**
 * Logical OR.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_logical_or(PEXPR pThis)
{
    PEXPRVAR    pVar1 = &pThis->aVars[pThis->iVar - 1];
    PEXPRVAR    pVar2 = &pThis->aVars[pThis->iVar];

    if (   expr_var_make_bool(pVar1)
        || expr_var_make_bool(pVar2))
        expr_var_assign_bool(pVar1, 1);
    else
        expr_var_assign_bool(pVar1, 0);

    expr_pop_and_delete_var(pThis);
    return kExprRet_Ok;
}


/**
 * Left parenthesis.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_left_parenthesis(PEXPR pThis)
{
    /*
     * There should be a right parenthesis operator lined up for us now,
     * eat it. If not found there is an inbalance.
     */
    EXPRRET rc = expr_get_binary_or_eoe_or_rparen(pThis);
    if (    rc == kExprRet_Operator
        &&  pThis->apOps[pThis->iOp]->szOp[0] == ')')
    {
        /* pop it and get another one which we can leave pending. */
        pThis->iOp--;
        rc = expr_get_binary_or_eoe_or_rparen(pThis);
        if (rc >= kExprRet_Ok)
            expr_unget_op(pThis);
    }
    else
    {
        expr_error(pThis, "Missing ')'");
        rc = kExprRet_Error;
    }

    return rc;
}


/**
 * Right parenthesis, dummy that's never actually called.
 *
 * @returns Status code.
 * @param   pThis       The instance.
 */
static EXPRRET expr_op_right_parenthesis(PEXPR pThis)
{
    assert(0);
    (void)pThis;
    return kExprRet_Ok;
}





/**
 * The operator table.
 *
 * This table is NOT ordered by precedence, but for linear search
 * allowing for first match to return the correct operator. This
 * means that || must come before |, or else | will match all.
 */
static const EXPROP g_aExprOps[] =
{
#define EXPR_OP(szOp, iPrecedence, cArgs, pfn)  {  szOp, sizeof(szOp) - 1, '\0', iPrecedence, cArgs, pfn }
    /*        Name, iPrecedence,  cArgs,    pfn    */
    EXPR_OP("defined",     90,      1,    expr_op_defined),
    EXPR_OP("exists",      90,      1,    expr_op_exists),
    EXPR_OP("target",      90,      1,    expr_op_target),
    EXPR_OP("bool",        90,      1,    expr_op_bool),
    EXPR_OP("num",         90,      1,    expr_op_num),
    EXPR_OP("strlen",      90,      1,    expr_op_strlen),
    EXPR_OP("str",         90,      1,    expr_op_str),
    EXPR_OP("+",           80,      1,    expr_op_pluss),
    EXPR_OP("-",           80,      1,    expr_op_minus),
    EXPR_OP("~",           80,      1,    expr_op_bitwise_not),
    EXPR_OP("*",           75,      2,    expr_op_multiply),
    EXPR_OP("/",           75,      2,    expr_op_divide),
    EXPR_OP("%",           75,      2,    expr_op_modulus),
    EXPR_OP("+",           70,      2,    expr_op_add),
    EXPR_OP("-",           70,      2,    expr_op_sub),
    EXPR_OP("<<",          65,      2,    expr_op_shift_left),
    EXPR_OP(">>",          65,      2,    expr_op_shift_right),
    EXPR_OP("<=",          60,      2,    expr_op_less_or_equal_than),
    EXPR_OP("<",           60,      2,    expr_op_less_than),
    EXPR_OP(">=",          60,      2,    expr_op_greater_or_equal_than),
    EXPR_OP(">",           60,      2,    expr_op_greater_than),
    EXPR_OP("vle",         60,      2,    expr_op_ver_less_or_equal_than),
    EXPR_OP("vlt",         60,      2,    expr_op_ver_less_than),
    EXPR_OP("vge",         60,      2,    expr_op_ver_greater_or_equal_than),
    EXPR_OP("vgt",         60,      2,    expr_op_ver_greater_than),
    EXPR_OP("==",          55,      2,    expr_op_equal),
    EXPR_OP("veq",         55,      2,    expr_op_ver_equal),
    EXPR_OP("!=",          55,      2,    expr_op_not_equal),
    EXPR_OP("vne",         55,      2,    expr_op_ver_not_equal),
    EXPR_OP("!",           80,      1,    expr_op_logical_not),
    EXPR_OP("^",           45,      2,    expr_op_bitwise_xor),
    EXPR_OP("&&",          35,      2,    expr_op_logical_and),
    EXPR_OP("&",           50,      2,    expr_op_bitwise_and),
    EXPR_OP("||",          30,      2,    expr_op_logical_or),
    EXPR_OP("|",           40,      2,    expr_op_bitwise_or),
          { "(", 1, ')',   10,      1,    expr_op_left_parenthesis },
          { ")", 1, '(',   10,      0,    expr_op_right_parenthesis },
 /*       { "?", 1, ':',    5,      2,    expr_op_question },
          { ":", 1, '?',    5,      2,    expr_op_colon }, -- too weird for now. */
#undef EXPR_OP
};

/** Dummy end of expression fake. */
static const EXPROP g_ExprEndOfExpOp =
{
            "", 0, '\0',    0,      0,    NULL
};


/**
 * Initializes the opcode character map if necessary.
 */
static void expr_map_init(void)
{
    unsigned i;
    if (g_fExprInitializedMap)
        return;

    /*
     * Initialize it.
     */
    memset(&g_auchOpStartCharMap, 0, sizeof(g_auchOpStartCharMap));
    for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
    {
        unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0];
        if (!g_auchOpStartCharMap[ch])
        {
            g_auchOpStartCharMap[ch] = (i << 2) | 1;
            if (!isalpha(ch))
                g_auchOpStartCharMap[ch] |= 2; /* Need no clear separation from operands. */
        }
    }

    /* whitespace (assumes C-like locale because I'm lazy): */
#define SET_WHITESPACE(a_ch) do {  \
        assert(g_auchOpStartCharMap[(unsigned char)(a_ch)] == 0); \
        g_auchOpStartCharMap[(unsigned char)(a_ch)] |= 2; \
    } while (0)
    SET_WHITESPACE(' ');
    SET_WHITESPACE('\t');
    SET_WHITESPACE('\n');
    SET_WHITESPACE('\r');
    SET_WHITESPACE('\v');
    SET_WHITESPACE('\f');

    g_fExprInitializedMap = 1;
}


/**
 * Looks up a character in the map.
 *
 * @returns the value for that char, see g_auchOpStartCharMap for details.
 * @param   ch      The character.
 */
static unsigned char expr_map_get(char ch)
{
    return g_auchOpStartCharMap[(unsigned char)ch];
}


/**
 * Searches the operator table given a potential operator start char.
 *
 * @returns Pointer to the matching operator. NULL if not found.
 * @param   psz     Pointer to what can be an operator.
 * @param   uchVal  The expr_map_get value.
 * @param   fUnary  Whether it must be an unary operator or not.
 */
static PCEXPROP expr_lookup_op(char const *psz, unsigned char uchVal, int fUnary)
{
    char ch = *psz;
    unsigned i;
    assert((uchVal & 2) == (isalpha(ch) ? 0 : 2));

    for (i = uchVal >> 2; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
    {
        /* compare the string... */
        if (g_aExprOps[i].szOp[0] != ch)
            continue;
        switch (g_aExprOps[i].cchOp)
        {
            case 1:
                break;
            case 2:
                if (g_aExprOps[i].szOp[1] != psz[1])
                    continue;
                break;
            default:
                if (strncmp(&g_aExprOps[i].szOp[1], psz + 1, g_aExprOps[i].cchOp - 1))
                    continue;
                break;
        }

        /* ... and the operator type. */
        if (fUnary == (g_aExprOps[i].cArgs == 1))
        {
            /* Check if we've got the needed operand separation: */
            if (   (uchVal & 2)
                || EXPR_IS_OP_SEPARATOR(psz[g_aExprOps[i].cchOp]))
            {
                /* got a match! */
                return &g_aExprOps[i];
            }
        }
    }

    return NULL;
}


/**
 * Ungets a binary operator.
 *
 * The operator is poped from the stack and put in the pending position.
 *
 * @param   pThis       The evaluator instance.
 */
static void expr_unget_op(PEXPR pThis)
{
    assert(pThis->pPending == NULL);
    assert(pThis->iOp >= 0);

    pThis->pPending = pThis->apOps[pThis->iOp];
    pThis->apOps[pThis->iOp] = NULL;
    pThis->iOp--;
}



/**
 * Get the next token, it should be a binary operator, or the end of
 * the expression, or a right parenthesis.
 *
 * The operator is pushed onto the stack and the status code indicates
 * which of the two we found.
 *
 * @returns status code. Will grumble on failure.
 * @retval  kExprRet_EndOfExpr if we encountered the end of the expression.
 * @retval  kExprRet_Operator if we encountered a binary operator or right
 *          parenthesis. It's on the operator stack.
 *
 * @param   pThis       The evaluator instance.
 */
static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis)
{
    /*
     * See if there is anything pending first.
     */
    PCEXPROP pOp = pThis->pPending;
    if (pOp)
        pThis->pPending = NULL;
    else
    {
        /*
         * Eat more of the expression.
         */
        char const *psz = pThis->psz;

        /* spaces */
        unsigned char uchVal;
        char ch;
        while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
            psz++;

        /* see what we've got. */
        if (ch)
        {
            if (uchVal & 1)
                pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
            if (!pOp)
            {
                expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz);
                return kExprRet_Error;
            }
            psz += pOp->cchOp;
        }
        else
            pOp = &g_ExprEndOfExpOp;
        pThis->psz = psz;
    }

    /*
     * Push it.
     */
    if (pThis->iOp >= EXPR_MAX_OPERATORS - 1)
    {
        expr_error(pThis, "Operator stack overflow");
        return kExprRet_Error;
    }
    pThis->apOps[++pThis->iOp] = pOp;

    return pOp->iPrecedence
         ? kExprRet_Operator
         : kExprRet_EndOfExpr;
}



/**
 * Get the next token, it should be an unary operator or an operand.
 *
 * This will fail if encountering the end of the expression since
 * it is implied that there should be something more.
 *
 * The token is pushed onto the respective stack and the status code
 * indicates which it is.
 *
 * @returns status code. On failure we'll be done bitching already.
 * @retval  kExprRet_Operator if we encountered an unary operator.
 *          It's on the operator stack.
 * @retval  kExprRet_Operand if we encountered an operand operator.
 *          It's on the operand stack.
 *
 * @param   This        The evaluator instance.
 */
static EXPRRET expr_get_unary_or_operand(PEXPR pThis)
{
    EXPRRET       rc;
    unsigned char uchVal;
    PCEXPROP      pOp;
    char const   *psz = pThis->psz;
    char          ch;

    /*
     * Eat white space and make sure there is something after it.
     */
    while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
        psz++;
    if (ch == '\0')
    {
        expr_error(pThis, "Unexpected end of expression");
        return kExprRet_Error;
    }

    /*
     * Is it an operator?
     */
    pOp = NULL;
    if (uchVal & 1)
        pOp = expr_lookup_op(psz, uchVal, 1 /* fUnary */);
    if (pOp)
    {
        /*
         * Push the operator onto the stack.
         */
        if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
        {
            pThis->apOps[++pThis->iOp] = pOp;
            rc = kExprRet_Operator;
        }
        else
        {
            expr_error(pThis, "Operator stack overflow");
            rc = kExprRet_Error;
        }
        psz += pOp->cchOp;
    }
    else if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
    {
        /*
         * It's an operand. Figure out where it ends and
         * push it onto the stack.
         */
        const char *pszStart;

        rc = kExprRet_Ok;
        if (ch == '"')
        {
            pszStart = ++psz;
            while ((ch = *psz) != '\0' && ch != '"')
                psz++;
            expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString);
            if (ch != '\0')
                psz++;
        }
        else if (ch == '\'')
        {
            pszStart = ++psz;
            while ((ch = *psz) != '\0' && ch != '\'')
                psz++;
            expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedSimpleString);
            if (ch != '\0')
                psz++;
        }
        else
        {
            char    achPars[20];
            int     iPar = -1;
            char    chEndPar = '\0';

            pszStart = psz;
            while ((ch = *psz) != '\0')
            {
                char ch2;

                /* $(adsf) or ${asdf} needs special handling. */
                if (    ch == '$'
                    &&  (   (ch2 = psz[1]) == '('
                         || ch2 == '{'))
                {
                    psz++;
                    if (iPar > (int)(sizeof(achPars) / sizeof(achPars[0])))
                    {
                        expr_error(pThis, "Too deep nesting of variable expansions");
                        rc = kExprRet_Error;
                        break;
                    }
                    achPars[++iPar] = chEndPar = ch2 == '(' ? ')' : '}';
                }
                else if (ch == chEndPar)
                {
                    iPar--;
                    chEndPar = iPar >= 0 ? achPars[iPar] : '\0';
                }
                else if (!chEndPar)
                {
                    uchVal = expr_map_get(ch);
                    if (uchVal == 0)
                    { /*likely*/ }
                    else if ((uchVal & 3) == 2 /*isspace*/)
                        break;
                    else if (   (uchVal & 1)
                             && psz != pszStart  /* not at the start */
                             && (   (uchVal & 2) /* operator without separator needs */
                                 || EXPR_IS_OP_SEPARATOR_NO_SPACE(psz[-1])))
                    {
                        pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
                        if (pOp)
                            break;
                    }
                }

                /* next */
                psz++;
            }

            if (rc == kExprRet_Ok)
                expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String);
        }
    }
    else
    {
        expr_error(pThis, "Operand stack overflow");
        rc = kExprRet_Error;
    }
    pThis->psz = psz;

    return rc;
}


/**
 * Evaluates the current expression.
 *
 * @returns status code.
 *
 * @param   pThis       The instance.
 */
static EXPRRET expr_eval(PEXPR pThis)
{
    EXPRRET  rc;
    PCEXPROP pOp;

    /*
     * The main loop.
     */
    for (;;)
    {
        /*
         * Eat unary operators until we hit an operand.
         */
        do  rc = expr_get_unary_or_operand(pThis);
        while (rc == kExprRet_Operator);
        if (rc < kExprRet_Ok)
            break;

        /*
         * Look for a binary operator, right parenthesis or end of expression.
         */
        rc = expr_get_binary_or_eoe_or_rparen(pThis);
        if (rc < kExprRet_Ok)
            break;
        expr_unget_op(pThis);

        /*
         * Pop operators and apply them.
         *
         * Parenthesis will be handed via precedence, where the left parenthesis
         * will go pop the right one and make another operator pending.
         */
        while (   pThis->iOp >= 0
               && pThis->apOps[pThis->iOp]->iPrecedence >= pThis->pPending->iPrecedence)
        {
            pOp = pThis->apOps[pThis->iOp--];
            assert(pThis->iVar + 1 >= pOp->cArgs);
            rc = pOp->pfn(pThis);
            if (rc < kExprRet_Ok)
                break;
        }
        if (rc < kExprRet_Ok)
            break;

        /*
         * Get the next binary operator or end of expression.
         * There should be no right parenthesis here.
         */
        rc = expr_get_binary_or_eoe_or_rparen(pThis);
        if (rc < kExprRet_Ok)
            break;
        pOp = pThis->apOps[pThis->iOp];
        if (!pOp->iPrecedence)
            break;  /* end of expression */
        if (!pOp->cArgs)
        {
            expr_error(pThis, "Unexpected \"%s\"", pOp->szOp);
            rc = kExprRet_Error;
            break;
        }
    }

    return rc;
}


/**
 * Destroys the given instance.
 *
 * @param   pThis       The instance to destroy.
 */
static void expr_destroy(PEXPR pThis)
{
    while (pThis->iVar >= 0)
    {
        expr_var_delete(pThis->aVars);
        pThis->iVar--;
    }
    free(pThis);
}


/**
 * Instantiates an expression evaluator.
 *
 * @returns The instance.
 *
 * @param   pszExpr     What to parse.
 *                      This must stick around until expr_destroy.
 */
static PEXPR expr_create(char const *pszExpr)
{
    PEXPR pThis = (PEXPR)xmalloc(sizeof(*pThis));
    pThis->pszExpr = pszExpr;
    pThis->psz = pszExpr;
    pThis->pFileLoc = NULL;
    pThis->pPending = NULL;
    pThis->iVar = -1;
    pThis->iOp = -1;

    expr_map_init();
    return pThis;
}


/**
 * Evaluates the given if expression.
 *
 * @returns -1, 0 or 1. (GNU make conditional check convention, see read.c.)
 * @retval  -1 if the expression is invalid.
 * @retval  0 if the expression is true
 * @retval  1 if the expression is false.
 *
 * @param   line    The expression.
 * @param   flocp   The file location, used for errors.
 */
int expr_eval_if_conditionals(const char *line, const floc *flocp)
{
    /*
     * Instantiate the expression evaluator and let
     * it have a go at it.
     */
    int rc = -1;
    PEXPR pExpr = expr_create(line);
    pExpr->pFileLoc = flocp;
    if (expr_eval(pExpr) >= kExprRet_Ok)
    {
        /*
         * Convert the result (on top of the stack) to boolean and
         * set our return value accordingly.
         */
        if (expr_var_make_bool(&pExpr->aVars[0]))
            rc = 0;
        else
            rc = 1;
    }
    expr_destroy(pExpr);

    return rc;
}


/**
 * Evaluates the given expression and returns the result as a string.
 *
 * @returns variable buffer position.
 *
 * @param   o       The current variable buffer position.
 * @param   expr    The expression.
 */
char *expr_eval_to_string(char *o, const char *expr)
{
    /*
     * Instantiate the expression evaluator and let
     * it have a go at it.
     */
    PEXPR pExpr = expr_create(expr);
    if (expr_eval(pExpr) >= kExprRet_Ok)
    {
        /*
         * Convert the result (on top of the stack) to a string
         * and copy it out the variable buffer.
         */
        PEXPRVAR pVar = &pExpr->aVars[0];
        expr_var_make_simple_string(pVar);
        o = variable_buffer_output(o, pVar->uVal.psz, strlen(pVar->uVal.psz));
    }
    else
        o = variable_buffer_output(o, "<expression evaluation failed>", sizeof("<expression evaluation failed>") - 1);
    expr_destroy(pExpr);

    return o;
}


#endif /* CONFIG_WITH_IF_CONDITIONALS */

