/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "zbxexpression.h"

#include "zbxnum.h"
#include "zbxvariant.h"
#include "zbxexpr.h"
#include "zbxalgo.h"

/******************************************************************************
 *                                                                            *
 *                     Module for evaluating expressions                      *
 *                  ---------------------------------------                   *
 *                                                                            *
 * Global variables are used for efficiency reasons so that arguments do not  *
 * have to be passed to each of evaluate_termX() functions. For this reason,  *
 * too, this module is isolated into a separate file.                         *
 *                                                                            *
 * The priority of supported operators is as follows:                         *
 *                                                                            *
 *   - (unary)   evaluate_term8()                                             *
 *   not         evaluate_term7()                                             *
 *   * /         evaluate_term6()                                             *
 *   + -         evaluate_term5()                                             *
 *   < <= >= >   evaluate_term4()                                             *
 *   = <>        evaluate_term3()                                             *
 *   and         evaluate_term2()                                             *
 *   or          evaluate_term1()                                             *
 *                                                                            *
 * Function evaluate_term9() is used for parsing tokens on the lowest level:  *
 * those can be suffixed numbers like "12.345K" or parenthesized expressions. *
 *                                                                            *
 ******************************************************************************/

static const char	*ptr;		/* character being looked at */
static int		level;		/* expression nesting level  */

static char		*buffer;	/* error message buffer      */
static size_t		max_buffer_len;	/* error message buffer size */

/******************************************************************************
 *                                                                            *
 * Purpose: check whether the character delimits a numeric token.             *
 *                                                                            *
 ******************************************************************************/
static int	is_number_delimiter(char c)
{
	return 0 == isdigit(c) && '.' != c && 0 == isalpha(c) ? SUCCEED : FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check whether the character delimits a symbolic operator token.   *
 *                                                                            *
 ******************************************************************************/
static int	is_operator_delimiter(char c)
{
	return ' ' == c || '(' == c || '\r' == c || '\n' == c || '\t' == c || ')' == c || '\0' == c ? SUCCEED : FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate a quoted string like "/etc/passwd". Characters '\' and   *
 * '"' are expected to be escaped or parsing fails                            *
 *                                                                            *
 ******************************************************************************/
static void	evaluate_string(zbx_variant_t *res)
{
	const char	*start;
	char		*res_temp = NULL, *dst;
	int		str_len = 0;

	for (start = ptr; '"' != *ptr; ptr++)
	{
		if ('\\' == *ptr)
		{
			ptr++;

			if ('\\' != *ptr && '\"' != *ptr && '\0' != *ptr)
			{
				zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
						" invalid escape sequence at \"%s\".", ptr - 1);
				zbx_variant_set_dbl(res, ZBX_INFINITY);
				return;
			}

		}

		if ('\0' == *ptr)
		{
			zbx_variant_set_dbl(res, ZBX_INFINITY);
			zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
					" unterminated string at \"%s\".", start);
			return;
		}
	}

	str_len = ptr - start;
	res_temp = zbx_malloc(NULL, str_len + 1);

	for (dst = res_temp; start != ptr; start++)
	{
		switch (*start)
		{
			case '\\':
				start++;
				break;
			case '\r':
				continue;
		}
		*dst++ = *start;
	}
	*dst = '\0';
	zbx_variant_set_str(res, res_temp);
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate a suffixed number like 12.345K.                          *
 *                                                                            *
 ******************************************************************************/
static double	evaluate_number(int *unknown_idx)
{
	double		result;
	int		len;

	/* Is it a special token of unknown value (e.g. ZBX_UNKNOWN0, ZBX_UNKNOWN1) ? */
	if (0 == strncmp(ZBX_UNKNOWN_STR, ptr, ZBX_UNKNOWN_STR_LEN))
	{
		const char	*p0, *p1;

		p0 = ptr + ZBX_UNKNOWN_STR_LEN;
		p1 = p0;

		/* extract the message number which follows after 'ZBX_UNKNOWN' */
		while (0 != isdigit((unsigned char)*p1))
			p1++;

		if (p0 < p1 && SUCCEED == is_number_delimiter(*p1))
		{
			ptr = p1;

			/* return 'unknown' and corresponding message number about its origin */
			*unknown_idx = atoi(p0);
			return ZBX_UNKNOWN;
		}

		ptr = p0;

		return ZBX_INFINITY;
	}

	if (SUCCEED == zbx_suffixed_number_parse(ptr, &len) && SUCCEED == is_number_delimiter(*(ptr + len)))
	{
		result = atof(ptr) * suffix2factor(*(ptr + len - 1));
		ptr += len;
	}
	else
		result = ZBX_INFINITY;

	return result;
}

/******************************************************************************
 *                                                                            *
 * Purpose: cast string variant to a double variant.                          *
 *                                                                            *
 * Parameters: var - [IN/OUT] the variant to cast                             *
 *                                                                            *
 ******************************************************************************/
static void	variant_convert_to_double(zbx_variant_t *var)
{
	if (ZBX_VARIANT_STR == var->type)
	{
		double	var_double_value = zbx_evaluate_string_to_double(var->data.str);
		if (ZBX_INFINITY == var_double_value)
		{
			zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
					" value \"%s\" is not a numeric operand.", var->data.str);
		}
		zbx_variant_clear(var);
		zbx_variant_set_dbl(var, var_double_value);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: get variant value in double (float64) format.                     *
 *                                                                            *
 * Parameters: var - [IN] the input variant                                   *
 *                                                                            *
 * Return value: depending on variant type:                                   *
 *    DBL - the variant value                                                 *
 *    STR - if variant value contains valid float64 string (with supported    *
 *          Zabbix suffixes) the converted value is returned. Otherwise       *
 *          ZBX_INFINITY is returned.                                         *
 *    other types - ZBX_INFINITY                                              *
 *                                                                            *
 ******************************************************************************/
static double	variant_get_double(const zbx_variant_t *var)
{
	switch (var->type)
	{
		case ZBX_VARIANT_DBL:
			return var->data.dbl;
		case ZBX_VARIANT_STR:
			return zbx_evaluate_string_to_double(var->data.str);
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			return ZBX_INFINITY;
	}
}

static zbx_variant_t	evaluate_term1(int *unknown_idx);

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate a suffixed number or a parenthesized expression.         *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term9(int *unknown_idx)
{
	zbx_variant_t	res;

	while (' ' == *ptr || '\r' == *ptr || '\n' == *ptr || '\t' == *ptr)
		ptr++;

	if ('\0' == *ptr)
	{
		zbx_strlcpy(buffer, "Cannot evaluate expression: unexpected end of expression.", max_buffer_len);
		zbx_variant_set_dbl(&res, ZBX_INFINITY);
		return res;
	}

	if ('(' == *ptr)
	{
		ptr++;

		res = evaluate_term1(unknown_idx);

		if (ZBX_VARIANT_DBL == res.type && ZBX_INFINITY == res.data.dbl)
			return res;

		/* if evaluate_term1() returns ZBX_UNKNOWN then continue as with regular number */

		if (')' != *ptr)
		{
			zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
					" expected closing parenthesis at \"%s\".", ptr);

			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);

			return res;
		}

		ptr++;
	}
	else
	{
		if ('"' == *ptr)
		{
			ptr++;
			evaluate_string(&res);

			if (ZBX_VARIANT_DBL == res.type && ZBX_INFINITY == res.data.dbl)
				return res;

			ptr++;

			/* We do not really need to do this check. */
			/* The only reason we do it is to keep it consistent with */
			/* numeric tokens, where operators are not allowed after them: 123and. */
			/* Check below ensures that '"123"and' expression will fail as well. */
			if (FAIL == is_operator_delimiter(*ptr) && FAIL == is_number_delimiter(*ptr))
			{
				zbx_variant_clear(&res);
				zbx_variant_set_dbl(&res, ZBX_INFINITY);
				zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
						" unexpected token at \"%s\".", ptr);
			}

		}
		else
		{
			zbx_variant_set_dbl(&res, evaluate_number(unknown_idx));

			if (ZBX_INFINITY == res.data.dbl)
			{
				zbx_snprintf(buffer, max_buffer_len, "Cannot evaluate expression:"
						" expected numeric token at \"%s\".", ptr);
				return res;
			}
		}
	}

	while ('\0' != *ptr && (' ' == *ptr || '\r' == *ptr || '\n' == *ptr || '\t' == *ptr))
		ptr++;

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "-" (unary).                                             *
 *                                                                            *
 * -0.0     -> -0.0                                                           *
 * -1.2     -> -1.2                                                           *
 * -Unknown ->  Unknown                                                       *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term8(int *unknown_idx)
{
	while (' ' == *ptr || '\r' == *ptr || '\n' == *ptr || '\t' == *ptr)
		ptr++;

	if ('-' == *ptr)
	{
		zbx_variant_t	res;
		ptr++;
		res = evaluate_term9(unknown_idx);
		variant_convert_to_double(&res);

		if (ZBX_UNKNOWN == res.data.dbl || ZBX_INFINITY == res.data.dbl)
			return res;

		res.data.dbl = -res.data.dbl;
		return res;
	}
	else
		return evaluate_term9(unknown_idx);
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "not".                                                   *
 *                                                                            *
 * not 0.0     ->  1.0                                                        *
 * not 1.2     ->  0.0                                                        *
 * not Unknown ->  Unknown                                                    *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term7(int *unknown_idx)
{
	while (' ' == *ptr || '\r' == *ptr || '\n' == *ptr || '\t' == *ptr)
		ptr++;

	if ('n' == ptr[0] && 'o' == ptr[1] && 't' == ptr[2] && SUCCEED == is_operator_delimiter(ptr[3]))
	{
		zbx_variant_t	res;
		ptr += 3;
		res = evaluate_term8(unknown_idx);
		variant_convert_to_double(&res);

		if (ZBX_UNKNOWN == res.data.dbl || ZBX_INFINITY == res.data.dbl)
			return res;

		res.data.dbl = (SUCCEED == zbx_double_compare(res.data.dbl, 0.0) ? 1.0 : 0.0);
		return res;
	}
	else
		return evaluate_term8(unknown_idx);
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "*" and "/".                                             *
 *                                                                            *
 *     0.0 * Unknown  ->  Unknown (yes, not 0 as we don't want to lose        *
 *                        Unknown in arithmetic operations)                   *
 *     1.2 * Unknown  ->  Unknown                                             *
 *     0.0 / 1.2      ->  0.0                                                 *
 *     1.2 / 0.0      ->  error (ZBX_INFINITY)                                *
 * Unknown / 0.0      ->  error (ZBX_INFINITY)                                *
 * Unknown / 1.2      ->  Unknown                                             *
 * Unknown / Unknown  ->  Unknown                                             *
 *     0.0 / Unknown  ->  Unknown                                             *
 *     1.2 / Unknown  ->  Unknown                                             *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term6(int *unknown_idx)
{
	char		op;
	int		res_idx = -1, oper_idx = -2;	/* set invalid values to catch errors */
	zbx_variant_t	res;

	res = evaluate_term7(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term7() returns ZBX_UNKNOWN then continue as with regular number */

	while ('*' == *ptr || '/' == *ptr)
	{
		zbx_variant_t	operand;

		variant_convert_to_double(&res);

		if (ZBX_INFINITY == res.data.dbl)
			return res;

		op = *ptr++;

		/* 'ZBX_UNKNOWN' in multiplication and division produces 'ZBX_UNKNOWN'. */
		/* Even if 1st operand is Unknown we evaluate 2nd operand too to catch fatal errors in it. */

		operand = evaluate_term7(&oper_idx);
		variant_convert_to_double(&operand);

		if (ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
			zbx_variant_clear(&operand);
			return res;
		}

		if ('*' == op)
		{
			if (ZBX_UNKNOWN == operand.data.dbl)		/* (anything) * Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
			else if (ZBX_UNKNOWN == res.data.dbl)		/* Unknown * known */
				*unknown_idx = res_idx;
			else
				res.data.dbl *= operand.data.dbl;
		}
		else
		{
			/* catch division by 0 even if 1st operand is Unknown */

			if (ZBX_UNKNOWN != operand.data.dbl && SUCCEED == zbx_double_compare(operand.data.dbl, 0.0))
			{
				zbx_strlcpy(buffer, "Cannot evaluate expression: division by zero.", max_buffer_len);
				zbx_variant_clear(&res);
				zbx_variant_set_dbl(&res, ZBX_INFINITY);
				zbx_variant_clear(&operand);
				return res;
			}

			if (ZBX_UNKNOWN == operand.data.dbl)		/* (anything) / Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
			else if (ZBX_UNKNOWN == res.data.dbl)		/* Unknown / known */
			{
				*unknown_idx = res_idx;
			}
			else
				res.data.dbl /= operand.data.dbl;
		}

		zbx_variant_clear(&operand);
	}
	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "+" and "-".                                             *
 *                                                                            *
 *     0.0 +/- Unknown  ->  Unknown                                           *
 *     1.2 +/- Unknown  ->  Unknown                                           *
 * Unknown +/- Unknown  ->  Unknown                                           *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term5(int *unknown_idx)
{
	char		op;
	zbx_variant_t	res, operand;
	int		res_idx = -3, oper_idx = -4;	/* set invalid values to catch errors */

	res = evaluate_term6(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term6() returns ZBX_UNKNOWN then continue as with regular number */

	while ('+' == *ptr || '-' == *ptr)
	{
		variant_convert_to_double(&res);

		if (ZBX_INFINITY == res.data.dbl)
			return res;

		op = *ptr++;

		/* even if 1st operand is Unknown we evaluate 2nd operand to catch fatal error if any occurs */

		operand = evaluate_term6(&oper_idx);
		variant_convert_to_double(&operand);

		if (ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
			zbx_variant_clear(&operand);
			return res;
		}

		if (ZBX_UNKNOWN == operand.data.dbl)		/* (anything) +/- Unknown */
		{
			*unknown_idx = oper_idx;
			res_idx = oper_idx;
			res.data.dbl = ZBX_UNKNOWN;
		}
		else if (ZBX_UNKNOWN == res.data.dbl)		/* Unknown +/- known */
		{
			*unknown_idx = res_idx;
		}
		else
		{
			if ('+' == op)
				res.data.dbl += operand.data.dbl;
			else
				res.data.dbl -= operand.data.dbl;
		}

		zbx_variant_clear(&operand);
	}

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "<", "<=", ">=", ">".                                    *
 *                                                                            *
 *     0.0 < Unknown  ->  Unknown                                             *
 *     1.2 < Unknown  ->  Unknown                                             *
 * Unknown < Unknown  ->  Unknown                                             *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term4(int *unknown_idx)
{
	char		op;
	zbx_variant_t	res, operand;
	int		res_idx = -5, oper_idx = -6;	/* set invalid values to catch errors */

	res = evaluate_term5(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term5() returns ZBX_UNKNOWN then continue as with regular number */

	while (1)
	{
		if ('<' == ptr[0] && '=' == ptr[1])
		{
			op = 'l';
			ptr += 2;
		}
		else if ('>' == ptr[0] && '=' == ptr[1])
		{
			op = 'g';
			ptr += 2;
		}
		else if (('<' == ptr[0] && '>' != ptr[1]) || '>' == ptr[0])
		{
			op = *ptr++;
		}
		else
			break;

		variant_convert_to_double(&res);

		if (ZBX_INFINITY == res.data.dbl)
			return res;

		/* even if 1st operand is Unknown we evaluate 2nd operand to catch fatal error if any occurs */

		operand = evaluate_term5(&oper_idx);

		variant_convert_to_double(&operand);

		if (ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
			zbx_variant_clear(&operand);
			return res;
		}

		if (ZBX_UNKNOWN == operand.data.dbl)		/* (anything) < Unknown */
		{
			*unknown_idx = oper_idx;
			res_idx = oper_idx;
			res.data.dbl = ZBX_UNKNOWN;
		}
		else if (ZBX_UNKNOWN == res.data.dbl)		/* Unknown < known */
		{
			*unknown_idx = res_idx;
		}
		else
		{
			if ('<' == op)
				res.data.dbl = (res.data.dbl < operand.data.dbl - zbx_get_double_epsilon());
			else if ('l' == op)
				res.data.dbl = (res.data.dbl <= operand.data.dbl + zbx_get_double_epsilon());
			else if ('g' == op)
				res.data.dbl = (res.data.dbl >= operand.data.dbl - zbx_get_double_epsilon());
			else
				res.data.dbl = (res.data.dbl > operand.data.dbl + zbx_get_double_epsilon());
		}

		zbx_variant_clear(&operand);
	}

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "=" and "<>".                                            *
 *                                                                            *
 *      0.0 = Unknown  ->  Unknown                                            *
 *      1.2 = Unknown  ->  Unknown                                            *
 *  Unknown = Unknown  ->  Unknown                                            *
 *     0.0 <> Unknown  ->  Unknown                                            *
 *     1.2 <> Unknown  ->  Unknown                                            *
 * Unknown <> Unknown  ->  Unknown                                            *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term3(int *unknown_idx)
{
	char		op;
	int		res_idx = -7, oper_idx = -8;	/* set invalid values to catch errors */
	zbx_variant_t	res, operand;
	double		left, right, value;

	res = evaluate_term4(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term4() returns ZBX_UNKNOWN then continue as with regular number */

	while (1)
	{
		if ('=' == *ptr)
		{
			op = *ptr++;
		}
		else if ('<' == ptr[0] && '>' == ptr[1])
		{
			op = '#';
			ptr += 2;
		}
		else
			break;

		/* even if 1st operand is Unknown we evaluate 2nd operand to catch fatal error if any occurs */

		operand = evaluate_term4(&oper_idx);

		if (ZBX_VARIANT_DBL == operand.type && ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			return operand;
		}

		if (ZBX_VARIANT_DBL == res.type && ZBX_UNKNOWN == res.data.dbl)
		{
			zbx_variant_clear(&operand);
			continue;
		}

		if (ZBX_VARIANT_DBL == operand.type && ZBX_UNKNOWN == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			res = operand;
			*unknown_idx = oper_idx;
			continue;
		}

		left = variant_get_double(&res);
		right = variant_get_double(&operand);

		if (ZBX_INFINITY != left && ZBX_INFINITY != right)
		{
			/* both operands either are of double type or could be cast to it - */
			/* compare them as double values                                    */
			value = (SUCCEED == zbx_double_compare(left, right) ? 1 : 0);
		}
		else if (ZBX_VARIANT_DBL == res.type || ZBX_VARIANT_DBL == operand.type)
		{
			/* if one of operands has double type and the other */
			/* cannot be cast to double - they cannot be equal  */
			value = 0;
		}
		else
		{
			/* at this point both operands should be strings and should be */
			/* compared as such but check for their types just in case     */
			if (ZBX_VARIANT_STR != res.type || ZBX_VARIANT_STR != operand.type)
			{
				zbx_strlcpy(buffer, "Cannot evaluate expression: unsupported value type found.",
						max_buffer_len);
				value = ZBX_INFINITY;
				THIS_SHOULD_NEVER_HAPPEN;
			}
			else
				value = !strcmp(res.data.str, operand.data.str);
		}

		if ('#' == op)
			value = (SUCCEED == zbx_double_compare(value, 0.0) ? 1.0 : 0.0);

		zbx_variant_clear(&res);
		zbx_variant_clear(&operand);
		zbx_variant_set_dbl(&res, value);
	}

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "and".                                                   *
 *                                                                            *
 *      0.0 and Unknown  -> 0.0                                               *
 *  Unknown and 0.0      -> 0.0                                               *
 *      1.0 and Unknown  -> Unknown                                           *
 *  Unknown and 1.0      -> Unknown                                           *
 *  Unknown and Unknown  -> Unknown                                           *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term2(int *unknown_idx)
{
	zbx_variant_t	res, operand;
	int		res_idx = -9, oper_idx = -10;	/* set invalid values to catch errors */

	res = evaluate_term3(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term3() returns ZBX_UNKNOWN then continue as with regular number */

	while ('a' == ptr[0] && 'n' == ptr[1] && 'd' == ptr[2] && SUCCEED == is_operator_delimiter(ptr[3]))
	{
		ptr += 3;
		variant_convert_to_double(&res);

		if (ZBX_INFINITY == res.data.dbl)
			return res;

		operand = evaluate_term3(&oper_idx);
		variant_convert_to_double(&operand);

		if (ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
			zbx_variant_clear(&operand);
			return res;
		}

		if (ZBX_UNKNOWN == res.data.dbl)
		{
			if (ZBX_UNKNOWN == operand.data.dbl)				/* Unknown and Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
			else if (SUCCEED == zbx_double_compare(operand.data.dbl, 0.0))	/* Unknown and 0 */
			{
				res.data.dbl = 0.0;
			}
			else							/* Unknown and 1 */
				*unknown_idx = res_idx;
		}
		else if (ZBX_UNKNOWN == operand.data.dbl)
		{
			if (SUCCEED == zbx_double_compare(res.data.dbl, 0.0))		/* 0 and Unknown */
			{
				res.data.dbl = 0.0;
			}
			else							/* 1 and Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
		}
		else
		{
			res.data.dbl = (SUCCEED != zbx_double_compare(res.data.dbl, 0.0) &&
					SUCCEED != zbx_double_compare(operand.data.dbl, 0.0));
		}

		zbx_variant_clear(&operand);
	}

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate "or".                                                    *
 *                                                                            *
 *      1.0 or Unknown  -> 1.0                                                *
 *  Unknown or 1.0      -> 1.0                                                *
 *      0.0 or Unknown  -> Unknown                                            *
 *  Unknown or 0.0      -> Unknown                                            *
 *  Unknown or Unknown  -> Unknown                                            *
 *                                                                            *
 ******************************************************************************/
static zbx_variant_t	evaluate_term1(int *unknown_idx)
{
	int		res_idx = -11, oper_idx = -12;	/* set invalid values to catch errors */
	zbx_variant_t	res, operand;

	level++;

	if (32 < level)
	{
		zbx_strlcpy(buffer, "Cannot evaluate expression: nesting level is too deep.", max_buffer_len);
		zbx_variant_set_dbl(&res, ZBX_INFINITY);
		return res;
	}

	res = evaluate_term2(&res_idx);

	if (ZBX_VARIANT_DBL == res.type)
	{
		if (ZBX_INFINITY == res.data.dbl)
			return res;

		if (ZBX_UNKNOWN == res.data.dbl)
			*unknown_idx = res_idx;
	}

	/* if evaluate_term2() returns ZBX_UNKNOWN then continue as with regular number */

	while ('o' == ptr[0] && 'r' == ptr[1] && SUCCEED == is_operator_delimiter(ptr[2]))
	{
		ptr += 2;
		variant_convert_to_double(&res);

		if (ZBX_INFINITY == res.data.dbl)
			return res;

		operand = evaluate_term2(&oper_idx);

		variant_convert_to_double(&operand);

		if (ZBX_INFINITY == operand.data.dbl)
		{
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
			zbx_variant_clear(&operand);
			return res;
		}

		if (ZBX_UNKNOWN == res.data.dbl)
		{
			if (ZBX_UNKNOWN == operand.data.dbl)				/* Unknown or Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
			else if (SUCCEED != zbx_double_compare(operand.data.dbl, 0.0))	/* Unknown or 1 */
			{
				res.data.dbl = 1;
			}
			else							/* Unknown or 0 */
				*unknown_idx = res_idx;
		}
		else if (ZBX_UNKNOWN == operand.data.dbl)
		{
			if (SUCCEED != zbx_double_compare(res.data.dbl, 0.0))		/* 1 or Unknown */
			{
				res.data.dbl = 1;
			}
			else							/* 0 or Unknown */
			{
				*unknown_idx = oper_idx;
				res_idx = oper_idx;
				res.data.dbl = ZBX_UNKNOWN;
			}
		}
		else
		{
			res.data.dbl = (SUCCEED != zbx_double_compare(res.data.dbl, 0.0) ||
					SUCCEED != zbx_double_compare(operand.data.dbl, 0.0));
		}
		zbx_variant_clear(&operand);
	}

	level--;

	return res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: evaluate an expression like "(26.416>10) or (0=1)".               *
 *                                                                            *
 ******************************************************************************/
int	zbx_evaluate(double *value, const char *expression, char *error, size_t max_error_len,
		zbx_vector_str_t *unknown_msgs)
{
	int		unknown_idx = -13;	/* index of message in 'unknown_msgs' vector, set to invalid value */
						/* to catch errors */
	zbx_variant_t	res;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() expression:'%s'", __func__, expression);

	ptr = expression;
	level = 0;
	buffer = error;
	max_buffer_len = max_error_len;
	res = evaluate_term1(&unknown_idx);

	if (ZBX_VARIANT_STR == res.type)
	{
		if (0 == strlen(res.data.str))
		{
			zbx_strlcpy(buffer, "Cannot evaluate expression: unexpected end of expression.",
					max_buffer_len);
			zbx_variant_clear(&res);
			zbx_variant_set_dbl(&res, ZBX_INFINITY);
		}
		else
		{
			variant_convert_to_double(&res);
		}
	}

	*value = res.data.dbl;
	zbx_variant_clear(&res);

	if ('\0' != *ptr && ZBX_INFINITY != *value)
	{
		zbx_snprintf(error, max_error_len, "Cannot evaluate expression: unexpected token at \"%s\".", ptr);
		*value = ZBX_INFINITY;
	}

	if (ZBX_UNKNOWN == *value)
	{
		/* Map Unknown result to error. Callers currently do not operate with ZBX_UNKNOWN. */
		if (NULL != unknown_msgs)
		{
			if (0 > unknown_idx)
			{
				THIS_SHOULD_NEVER_HAPPEN;
				zabbix_log(LOG_LEVEL_WARNING, "%s() internal error: " ZBX_UNKNOWN_STR " index:%d"
						" expression:'%s'", __func__, unknown_idx, expression);
				zbx_snprintf(error, max_error_len, "Internal error: " ZBX_UNKNOWN_STR " index %d."
						" Please report this to Zabbix developers.", unknown_idx);
			}
			else if (unknown_msgs->values_num > unknown_idx)
			{
				zbx_snprintf(error, max_error_len, "Cannot evaluate expression: \"%s\".",
						(char *)(unknown_msgs->values[unknown_idx]));
			}
			else
			{
				zbx_snprintf(error, max_error_len, "Cannot evaluate expression: unsupported "
						ZBX_UNKNOWN_STR "%d value.", unknown_idx);
			}
		}
		else
		{
			THIS_SHOULD_NEVER_HAPPEN;
			/* do not leave garbage in error buffer, write something helpful */
			zbx_snprintf(error, max_error_len, "%s(): internal error: no message for unknown result",
					__func__);
		}

		*value = ZBX_INFINITY;
	}

	if (ZBX_INFINITY == *value)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "End of %s() error:'%s'", __func__, error);
		return FAIL;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() value:" ZBX_FS_DBL, __func__, *value);

	return SUCCEED;
}

/*******************************************************************************
 *                                                                             *
 * Purpose: evaluate an expression like "(26.416>10) and not(0=ZBX_UNKNOWN0)". *
 *                                                                             *
 * Parameters: expression    - [IN]  expression to evaluate                    *
 *             value         - [OUT] expression evaluation result              *
 *             error         - [OUT] error message buffer                      *
 *             max_error_len - [IN]  error buffer size                         *
 *                                                                             *
 * Return value: SUCCEED - expression evaluated successfully,                  *
 *                         or evaluation result is undefined (ZBX_UNKNOWN)     *
 *               FAIL    - expression evaluation failed                        *
 *                                                                             *
 *******************************************************************************/
int	zbx_evaluate_unknown(const char *expression, double *value, char *error, size_t max_error_len)
{
	const char	*__function_name = "evaluate_with_unknown";
	zbx_variant_t	res;
	int		unknown_idx = -13;	/* index of message in 'unknown_msgs' vector, set to invalid value */
						/* to catch errors */

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() expression:'%s'", __function_name, expression);

	ptr = expression;
	level = 0;

	buffer = error;
	max_buffer_len = max_error_len;
	res = evaluate_term1(&unknown_idx);
	variant_convert_to_double(&res);
	*value = res.data.dbl;
	zbx_variant_clear(&res);

	if ('\0' != *ptr && ZBX_INFINITY != *value)
	{
		zbx_snprintf(error, max_error_len, "Cannot evaluate expression: unexpected token at \"%s\".", ptr);
		*value = ZBX_INFINITY;
	}

	if (ZBX_INFINITY == *value)
	{
		zabbix_log(LOG_LEVEL_DEBUG, "End of %s() error:'%s'", __function_name, error);
		return FAIL;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() value:" ZBX_FS_DBL, __function_name, *value);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: cast string to a double, expand suffixes and parse negative sign. *
 *                                                                            *
 * Parameters: in - [IN] the input string                                     *
 *                                                                            *
 * Return value: resulting double                                             *
 *                                                                            *
 ******************************************************************************/
double	zbx_evaluate_string_to_double(const char *in)
{
	int		len;
	double		result_double_value;
	const char	*tmp_ptr = in;

	if (1 < strlen(in) && '-' == in[0])
		tmp_ptr++;

	if (SUCCEED == zbx_suffixed_number_parse(tmp_ptr, &len) && '\0' == *(tmp_ptr + len))
	{
		result_double_value = atof(tmp_ptr) * suffix2factor(*(tmp_ptr + len - 1));

		/* negative sign detected */
		if (tmp_ptr != in)
			result_double_value = -(result_double_value);
	}
	else
	{
		result_double_value = ZBX_INFINITY;
	}

	return result_double_value;
}