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