/*
** 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 "zbxeval.h"
#include "eval.h"

#include "zbxstr.h"

ZBX_VECTOR_IMPL(eval_token, zbx_eval_token_t)

static int	is_whitespace(char c)
{
	return 0 != isspace((unsigned char)c) ? SUCCEED : FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: finds number of following whitespace characters                   *
 *                                                                            *
 * Parameters: ctx - [IN] evaluation context                                  *
 *             pos - [IN] starting position                                   *
 *                                                                            *
 * Return value: number of whitespace characters found                        *
 *                                                                            *
 ******************************************************************************/
static size_t	eval_get_whitespace_len(zbx_eval_context_t *ctx, size_t pos)
{
	const char	*ptr = ctx->expression + pos;

	while (SUCCEED == is_whitespace(*ptr))
		ptr++;

	return ptr - ctx->expression - pos;
}

/******************************************************************************
 *                                                                            *
 * Purpose: updates constant variable index in trigger expression             *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             token - [IN] variable token                                    *
 *                                                                            *
 * Comments: The index is used to refer constant values by using $<N> in      *
 *           trigger names. Function arguments are excluded.                  *
 *                                                                            *
 ******************************************************************************/
static void	eval_update_const_variable(zbx_eval_context_t *ctx, zbx_eval_token_t *token)
{
	zbx_variant_set_none(&token->value);

	if (0 != (ctx->rules & ZBX_EVAL_PARSE_CONST_INDEX))
	{
		int	i;

		for (i = 0; i < ctx->ops.values_num; i++)
		{
			if (0 != (ctx->ops.values[i].type & ZBX_EVAL_CLASS_FUNCTION))
				return;
		}

		token->opt = ctx->const_index++;
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: Checks if the character can be a part of a compound number        *
 *          following a macro.                                                *
 *                                                                            *
 ******************************************************************************/
static int	eval_is_compound_number_char(char c, int pos)
{
	if (0 != isdigit((unsigned char)c))
		return SUCCEED;

	switch (c)
	{
		case '.':
		case '{':
			return SUCCEED;
		case 'e':
		case 'E':
			return (0 != pos ? SUCCEED : FAIL);
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses functionid token ({<functionid>})                          *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_functionid(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	zbx_token_t	tok;

	if (SUCCEED == zbx_token_parse_objectid(ctx->expression, ctx->expression + pos, &tok))
	{
		token->type = ZBX_EVAL_TOKEN_FUNCTIONID;
		token->opt = ctx->functionid_index++;
		token->loc = tok.loc;
		return SUCCEED;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses macro                                                      *
 *                                                                            *
 * Parameters: ctx - [IN] evaluation context                                  *
 *             pos - [IN] starting position                                   *
 *             tok - [OUT] parsed token                                       *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_macro(zbx_eval_context_t *ctx, int pos, zbx_token_t *tok)
{
	if (0 != (ctx->rules & ZBX_EVAL_PARSE_MACRO) &&
			SUCCEED == zbx_token_parse_macro(ctx->expression, ctx->expression + pos, tok))
	{
		return SUCCEED;
	}
	else if (0 != (ctx->rules & ZBX_EVAL_PARSE_USERMACRO) && '$' == ctx->expression[pos + 1] &&
			SUCCEED == zbx_token_parse_user_macro(ctx->expression, ctx->expression + pos, tok))
	{
		return SUCCEED;
	}
	else if (0 != (ctx->rules & ZBX_EVAL_PARSE_LLDMACRO) && '#' == ctx->expression[pos + 1] &&
			SUCCEED == zbx_token_parse_lld_macro(ctx->expression, ctx->expression + pos, tok))
	{
		return SUCCEED;
	}
	else if ('{' == ctx->expression[pos + 1] && SUCCEED == zbx_token_parse_nested_macro(ctx->expression,
			ctx->expression + pos, 0, tok))
	{
		return SUCCEED;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses numeric value                                              *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             pos_r - [OUT]                                                  *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_number(zbx_eval_context_t *ctx, size_t pos, size_t *pos_r)
{
	int		len, offset = 0;
	char		*end;
	double		tmp;

	if ('-' == ctx->expression[pos])
		offset++;

	if (FAIL == zbx_suffixed_number_parse(ctx->expression + pos + offset, &len))
		return FAIL;

	len += offset;

	tmp = strtod(ctx->expression + pos, &end) * (double)suffix2factor(ctx->expression[(int)pos + len - 1]);
	if (HUGE_VAL == tmp || -HUGE_VAL == tmp || EDOM == errno)
		return FAIL;

	*pos_r = pos + (size_t)len - 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses constant value                                             *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *             error - [OUT] error message                                    *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: A constant is a number or macro depending on parsing rules.      *
 *           Number can be a compound value, consisting of several macros     *
 *           digits etc.                                                      *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_constant(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
	zbx_token_t		tok;
	size_t			offset = pos;
	zbx_token_type_t	type = 0, last_type = 0;

	do
	{
		if ('{' == (ctx->expression[offset]))
		{
			last_type = ZBX_TOKEN_MACRO;

			if (SUCCEED != eval_parse_macro(ctx, (int)offset, &tok))
				break;

			if (pos == offset)
			{
				switch (tok.type)
				{
					case ZBX_TOKEN_MACRO:
					case ZBX_TOKEN_FUNC_MACRO:
					case ZBX_TOKEN_SIMPLE_MACRO:
						type = ZBX_EVAL_TOKEN_VAR_MACRO;
						break;
					case ZBX_TOKEN_USER_FUNC_MACRO:
					case ZBX_TOKEN_USER_MACRO:
						type = ZBX_EVAL_TOKEN_VAR_USERMACRO;
						break;
					case ZBX_TOKEN_LLD_MACRO:
					case ZBX_TOKEN_LLD_FUNC_MACRO:
						type = ZBX_EVAL_TOKEN_VAR_LLDMACRO;
						break;
				}
			}
			else
				type = ZBX_EVAL_TOKEN_VAR_NUM;

			offset = tok.loc.r + 1;

			switch (ctx->expression[offset])
			{
				case 's':
				case 'm':
				case 'h':
				case 'd':
				case 'w':
				case 'K':
				case 'M':
				case 'G':
				case 'T':
					type = ZBX_EVAL_TOKEN_VAR_NUM;
					offset++;
					goto out;
			}
		}
		else if (ZBX_EVAL_TOKEN_VAR_NUM != last_type && SUCCEED == eval_parse_number(ctx, offset, &offset))
		{
			last_type = type = ZBX_EVAL_TOKEN_VAR_NUM;
			offset++;
		}
		else if (SUCCEED == eval_is_compound_number_char(ctx->expression[offset], offset - pos))
			offset++;
		else
			break;
	}
	while (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPOUND_CONST));
out:
	if (0 == type)
	{
		*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
		return FAIL;
	}

	if (ZBX_EVAL_TOKEN_VAR_NUM == type && 0 == (ctx->rules & ZBX_EVAL_PARSE_VAR_NUM))
	{
		*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
		return FAIL;
	}

	if (ZBX_EVAL_TOKEN_VAR_NUM == type || ZBX_EVAL_TOKEN_VAR_USERMACRO == type)
		eval_update_const_variable(ctx, token);

	token->type = type;
	token->loc.l = pos;
	token->loc.r = offset - 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses single character token                                     *
 *                                                                            *
 * Parameters: pos   - [IN] starting position                                 *
 *             type  - [IN] token type                                        *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 ******************************************************************************/
static void	eval_parse_character_token(size_t pos, zbx_token_type_t type, zbx_eval_token_t *token)
{
	token->type = type;
	token->loc.l = pos;
	token->loc.r = pos;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses token starting with  '<'                                   *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: Tokens starting with '<' are '<', '<=' and '<>'.                 *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_less_character_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_EQ) && '>' == ctx->expression[pos + 1])
	{
		token->type = ZBX_EVAL_TOKEN_OP_NE;
	}
	else
	{
		if (0 == (ctx->rules & ZBX_EVAL_PARSE_COMPARE_SORT))
			return FAIL;

		if ('=' != ctx->expression[pos + 1])
		{
			eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_LT, token);
			return SUCCEED;
		}

		token->type = ZBX_EVAL_TOKEN_OP_LE;
	}

	token->loc.l = pos;
	token->loc.r = pos + 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses token starting with  '>'                                   *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Comments: Tokens starting with '>' are '>' and '>='.                       *
 *                                                                            *
 ******************************************************************************/
static void	eval_parse_greater_character_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	if ('=' == ctx->expression[pos + 1])
	{
		token->type = ZBX_EVAL_TOKEN_OP_GE;
		token->loc.l = pos;
		token->loc.r = pos + 1;
	}
	else
		eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_GT, token);
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses string variable token                                      *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *             error - [OUT] error message in case of failure                 *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: String variable token is token starting with '"'.                *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_string_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
	const char	*ptr = ctx->expression + pos + 1;

	for (; '\0' != *ptr; ptr++)
	{
		if (*ptr == '"')
		{
			token->type = ZBX_EVAL_TOKEN_VAR_STR;
			token->loc.l = pos;
			token->loc.r = ptr - ctx->expression;
			eval_update_const_variable(ctx, token);
			return SUCCEED;
		}

		if ('\\' == *ptr)
		{
			if (0 == (ZBX_EVAL_PARSE_STR_V64_COMPAT & ctx->rules) && '"' != ptr[1] && '\\' != ptr[1])
			{
				*error = zbx_dsprintf(*error, "invalid escape sequence in string starting with \"%s\"",
						ptr);
				return FAIL;
			}

			if (0 != (ZBX_EVAL_PARSE_STR_V64_COMPAT & ctx->rules) && '"' != ptr[1])
				continue;
			else
				ptr++;
		}
	}

	*error = zbx_dsprintf(*error, "unterminated string at \"%s\"", ctx->expression + pos);
	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses numeric variable token                                     *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 *  Comments: Time suffixes s,m,h,d,w are supported.                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_number_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	int		len, offset = 0;
	char		*end;
	double		tmp;

	if ('-' == ctx->expression[pos])
		offset++;

	if (FAIL == zbx_suffixed_number_parse(ctx->expression + pos + offset, &len))
		return FAIL;

	len += offset;

	token->type = ZBX_EVAL_TOKEN_VAR_NUM;

	tmp = strtod(ctx->expression + pos, &end) * suffix2factor(ctx->expression[pos + len - 1]);
	if (HUGE_VAL == tmp || -HUGE_VAL == tmp || EDOM == errno)
		return FAIL;

	token->loc.l = pos;
	token->loc.r = pos + len - 1;
	eval_update_const_variable(ctx, token);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses logical operation token                                    *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: Keywords are 'and', 'or' and 'not', followed by separator        *
 *           character (whitespace or '(').                                   *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_logic_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	if (0 == strncmp(ctx->expression + pos, "and", 3))
	{
		token->loc.r = pos + 2;
		token->type = ZBX_EVAL_TOKEN_OP_AND;
	}
	else if (0 == strncmp(ctx->expression + pos, "or", 2))
	{
		token->loc.r = pos + 1;
		token->type = ZBX_EVAL_TOKEN_OP_OR;
	}
	else if (0 == strncmp(ctx->expression + pos, "not", 3))
	{
		token->loc.r = pos + 2;
		token->type = ZBX_EVAL_TOKEN_OP_NOT;
	}
	else
		return FAIL;

	/* keyword must be followed by whitespace or opening parenthesis */
	if ('(' != ctx->expression[token->loc.r + 1] && SUCCEED != is_whitespace(ctx->expression[token->loc.r + 1]))
		return FAIL;

	token->loc.l = pos;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses function token                                             *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: Function token is non-keyword alpha characters followed by '('.  *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_function_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	const char	*ptr = ctx->expression + pos;

	while (0 != isalpha((unsigned char)*ptr) || '_' == *ptr || 0 != isdigit((unsigned char)*ptr))
		ptr++;

	if ('(' == *ptr)
	{
		token->type = ZBX_EVAL_TOKEN_FUNCTION;
		token->loc.l = pos;
		token->loc.r = ptr - ctx->expression - 1;
		token->opt = ctx->stack.values_num;
		return SUCCEED;
	}

	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses item query filter (?[group="xyz"])                         *
 *                                                                            *
 * Parameters: ptr - [IN]  filter to parse                                    *
 *                   [OUT] reference to next character after filter           *
 *                                                                            *
 * Return value: SUCCEED - filter was parsed successfully                     *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_query_filter(const char **ptr)
{
	const char	*filter = *ptr;

	if ('[' != *(++filter))
		return FAIL;

	filter++;

	while (']' != *filter)
	{
		if ('\0' == *filter)
			return FAIL;

		if ('"' == *filter)
		{
			while ('"' != *(++filter))
			{
				if ('\0' == *filter)
					return FAIL;

				if ('\\' == *filter && '\0' == *(++filter))
					return FAIL;
			}
		}

		filter++;
	}

	*ptr = ++filter;
	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses item query /host/key?[filter] into host, key and filter    *
 *          components                                                        *
 *                                                                            *
 * Parameters: str     - [IN] item query                                      *
 *             phost   - [OUT] reference to host or NULL (optional)           *
 *             pkey    - [OUT] reference to key                               *
 *             pfilter - [OUT] reference to filter or NULL                    *
 *                                                                            *
 * Return value: The number of parsed characters, 0 if there was an error.    *
 *                                                                            *
 ******************************************************************************/
size_t	eval_parse_query(const char *str, const char **phost, const char **pkey, const char **pfilter)
{
#define MVAR_HOST_HOST	"{HOST.HOST"
#define MVAR_ITEM_KEY	"{ITEM.KEY"

	const char	*host = str + 1, *key, *filter, *end;

	key = host;

	if ('*' == *key)
	{
		key++;
	}
	else if ('{' == *key)
	{
		if (0 == strncmp(key, MVAR_HOST_HOST, ZBX_CONST_STRLEN(MVAR_HOST_HOST)))
		{
			size_t	offset = 0;

			if ('}' == key[ZBX_CONST_STRLEN(MVAR_HOST_HOST)])
			{
				offset = 1;
			}
			else if (0 != isdigit((unsigned char)key[ZBX_CONST_STRLEN(MVAR_HOST_HOST)]) &&
				'}' == key[ZBX_CONST_STRLEN(MVAR_HOST_HOST) + 1])
			{
				offset = 2;
			}

			if (0 != offset)
				key += ZBX_CONST_STRLEN(MVAR_HOST_HOST) + offset;
		}
	}
	else if ('/' != *key)
	{
		while (SUCCEED == zbx_is_hostname_char(*key))
			key++;
	}

	if ('/' != *key)
		return 0;

	end = ++key;

	if ('*' == *key)
	{
		end++;
	}
	else if ('{' == *key)
	{
		if (0 == strncmp(key, MVAR_ITEM_KEY, ZBX_CONST_STRLEN(MVAR_ITEM_KEY)))
		{
			size_t	offset = 0;

			if ('}' == key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY)])
			{
				offset = 1;
			}
			else if (0 != isdigit((unsigned char)key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY)]) &&
					'}' == key[ZBX_CONST_STRLEN(MVAR_ITEM_KEY) + 1])
			{
				offset = 2;
			}

			if (0 != offset)
				end += ZBX_CONST_STRLEN(MVAR_ITEM_KEY) + offset;
		}
	}
	else if (SUCCEED != zbx_parse_key(&end))
		return 0;

	if (*end == '?')
	{
		filter = end;
		if (SUCCEED != eval_parse_query_filter(&end))
			return 0;
		filter += 2;
	}
	else
		filter = NULL;

	if (NULL != phost)
	{
		*phost = host;
		*pkey = key;
		*pfilter = filter;
	}

	return end - str;

#undef MVAR_HOST_HOST
#undef MVAR_ITEM_KEY
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses history query token                                        *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *             error - [OUT] error message in case of failure                 *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: History query token is the first argument of history functions   *
 *           to specify item(s) in format /host/key                           *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_query_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
	size_t	len;

	if (0 == (len = eval_parse_query(ctx->expression + pos, NULL, NULL, NULL)))
	{
		*error = zbx_dsprintf(*error, "invalid item query starting at \"%s\"", ctx->expression + pos);
		return FAIL;
	}

	token->type = ZBX_EVAL_TOKEN_ARG_QUERY;
	token->loc.l = pos;
	token->loc.r = pos + len - 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses time period token                                          *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *             error - [OUT] error message in case of failure                 *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: Time period token is the second argument of history functions    *
 *           to specify the history range in format <period>[:<timeshift>].   *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_period_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
	size_t	offset = pos;

	for (;'\0' != ctx->expression[offset]; offset++)
	{
		if ('{' == ctx->expression[offset] && 0 != (ctx->rules & ZBX_EVAL_PARSE_COMPOUND_CONST))
		{
			zbx_token_t	tok;

			if (SUCCEED == eval_parse_macro(ctx, offset, &tok))
				offset = tok.loc.r;
			continue;

		}
		if (',' == ctx->expression[offset] || ')' == ctx->expression[offset] ||
				SUCCEED == is_whitespace(ctx->expression[offset]))
		{
			token->type = ZBX_EVAL_TOKEN_ARG_PERIOD;
			token->loc.l = pos;
			token->loc.r = offset - 1;
			zbx_variant_set_none(&token->value);

			return SUCCEED;
		}
	}

	*error = zbx_dsprintf(*error, "unterminated function at \"%s\"", ctx->expression + pos);
	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses property token                                             *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: Keywords are 'and', 'or' and 'not', followed by separator        *
 *           character (whitespace or '(').                                   *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_property_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token)
{
	if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROP_TAG) && 0 == strncmp(ctx->expression + pos, "tag", 3))
	{
		token->loc.r = pos + 2;
		token->type = ZBX_EVAL_TOKEN_PROP_TAG;
	}
	else if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROP_GROUP) && 0 == strncmp(ctx->expression + pos, "group", 5))
	{
		token->loc.r = pos + 4;
		token->type = ZBX_EVAL_TOKEN_PROP_GROUP;
	}
	else
		return FAIL;

	token->loc.l = pos;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             pos   - [IN] starting position                                 *
 *             token - [OUT] parsed token                                     *
 *             error - [OUT] error message in case of failure                 *
 *                                                                            *
 * Return value: SUCCEED - token was parsed successfully                      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_token(zbx_eval_context_t *ctx, size_t pos, zbx_eval_token_t *token, char **error)
{
	size_t	skip;

	skip = eval_get_whitespace_len(ctx, pos);
	pos += skip;

	switch (ctx->expression[pos])
	{
		case '{':
			if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type &&
				ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
			{
				return eval_parse_period_token(ctx, pos, token, error);
			}

			if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTIONID) &&
					SUCCEED == eval_parse_functionid(ctx, pos, token))
			{
				return SUCCEED;
			}
			return eval_parse_constant(ctx, pos, token, error);
		case '+':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_ADD, token);
				return SUCCEED;
			}
			break;
		case '-':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR))
			{
				if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND) &&
						SUCCEED == eval_parse_number_token(ctx, pos, token))
				{
					return SUCCEED;
				}
			}
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
			{
				if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND))
					eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_MINUS, token);
				else
					eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_SUB, token);

				return SUCCEED;
			}
			break;
		case '/':
			if (ZBX_EVAL_TOKEN_GROUP_OPEN == ctx->last_token_type &&
					0 != (ctx->rules & ZBX_EVAL_PARSE_ITEM_QUERY))
			{
				zbx_eval_token_t	*func_token = NULL;

				if (2 <= ctx->ops.values_num)
					func_token = &ctx->ops.values[ctx->ops.values_num - 2];

				if (NULL == func_token || 0 == (func_token->type & ZBX_EVAL_CLASS_FUNCTION))
				{
					*error = zbx_dsprintf(*error, "item query must be first argument of a"
							" historical function at \"%s\"", ctx->expression + pos);
					return FAIL;
				}
				func_token->type = ZBX_EVAL_TOKEN_HIST_FUNCTION;
				return eval_parse_query_token(ctx, pos, token, error);
			}
			else if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_DIV, token);
				return SUCCEED;
			}
			break;
		case '*':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_MATH))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_MUL, token);
				return SUCCEED;
			}
			break;
		case '<':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE))
			{
				if (SUCCEED == eval_parse_less_character_token(ctx, pos, token))
					return SUCCEED;
			}
			break;
		case '>':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_SORT))
			{
				eval_parse_greater_character_token(ctx, pos, token);
				return SUCCEED;
			}
			break;
		case '=':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_COMPARE_EQ))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_OP_EQ, token);
				return SUCCEED;
			}
			break;
		case '(':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_GROUP))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_GROUP_OPEN, token);
				return SUCCEED;
			}
			break;
		case ')':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_GROUP))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_GROUP_CLOSE, token);
				return SUCCEED;
			}
			break;
		case '"':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR_STR))
				return eval_parse_string_token(ctx, pos, token, error);
			break;
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			/* after ',' there will be at least one value on the stack */
			if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type && 0 < ctx->stack.values_num &&
				ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
			{
				return eval_parse_period_token(ctx, pos, token, error);
			}
			ZBX_FALLTHROUGH;
		case '.':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_VAR_NUM))
				return eval_parse_constant(ctx, pos, token, error);
			break;
		case '#':
			if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type &&
				ZBX_EVAL_TOKEN_ARG_QUERY == ctx->stack.values[ctx->stack.values_num - 1].type)
			{
				return eval_parse_period_token(ctx, pos, token, error);
			}
			break;
		case ',':
			if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTION_ARGS))
			{
				eval_parse_character_token(pos, ZBX_EVAL_TOKEN_COMMA, token);
				return SUCCEED;
			}
			break;
		case '\0':
			return SUCCEED;
		default:
			if (0 != isalpha((unsigned char)ctx->expression[pos]))
			{
				/* logical operation must be separated by whitespace or '(', ')', ',' characters */
				if (0 != (ctx->rules & ZBX_EVAL_PARSE_LOGIC) &&
						(0 != skip || 0 != (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR) ||
						ZBX_EVAL_TOKEN_GROUP_CLOSE == ctx->last_token_type))
				{
					if (SUCCEED == eval_parse_logic_token(ctx, pos, token))
						return SUCCEED;
				}

				if (0 != (ctx->rules & ZBX_EVAL_PARSE_FUNCTION_NAME) &&
						SUCCEED == eval_parse_function_token(ctx, pos, token))
				{
					return SUCCEED;
				}

				if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY) &&
						SUCCEED == eval_parse_property_token(ctx, pos, token))
				{
					return SUCCEED;
				}
			}
			break;
	}

	*error = zbx_dsprintf(*error, "invalid token starting with \"%s\"", ctx->expression + pos);
	return FAIL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds operator/function token to evaluation stack                  *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             token - [IN] token to add                                      *
 *             error - [OUT]                                                  *
 *                                                                            *
 ******************************************************************************/
static int	eval_append_operator(zbx_eval_context_t *ctx, zbx_eval_token_t *token, char **error)
{
	if (0 != (token->type & ZBX_EVAL_CLASS_FUNCTION))
	{
		int			i, params = 0;

		for (i = (int)token->opt; i < ctx->stack.values_num; i++)
		{
			if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_FUNCTION))
				params -= (int)ctx->stack.values[i].opt - 1;
			else if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERAND))
				params++;
			else if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERATOR2))
				params--;
		}

		token->opt = params;
	}

	if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY))
	{
		zbx_eval_token_t	*prop = NULL, *value = NULL;

		if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_PROPERTY))
		{
			prop = &ctx->stack.values[ctx->stack.values_num - 1];

			if (2 > ctx->stack.values_num)
			{
				*error = zbx_dsprintf(*error, "missing comparison string for property at \"%s\"",
						ctx->expression + prop->loc.l);
				return FAIL;
			}

			value = &ctx->stack.values[ctx->stack.values_num - 2];
			if (0 == (value->type & ZBX_EVAL_CLASS_OPERAND))
			{
				*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
						" \"%s\"", ctx->expression + prop->loc.l);
				return FAIL;
			}

		}

		if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_OPERAND) &&
				0 != (token->type & ZBX_EVAL_CLASS_OPERATOR2))
		{
			if (0 != (ctx->stack.values[ctx->stack.values_num - 2].type & ZBX_EVAL_CLASS_PROPERTY))
			{
				prop = &ctx->stack.values[ctx->stack.values_num - 2];
				value = &ctx->stack.values[ctx->stack.values_num - 1];
			}
		}

		if (NULL != prop)
		{
			if ((ZBX_EVAL_TOKEN_VAR_STR != value->type && ZBX_EVAL_TOKEN_VAR_USERMACRO != value->type &&
					ZBX_EVAL_TOKEN_VAR_LLDMACRO != value->type))
			{
				*error = zbx_dsprintf(*error, "invalid value type compared with property at \"%s\"",
						ctx->expression + prop->loc.l);
				return FAIL;
			}

			if (ZBX_EVAL_TOKEN_OP_EQ != token->type && ZBX_EVAL_TOKEN_OP_NE != token->type)
			{
				*error = zbx_dsprintf(*error, "invalid operator used with property at \"%s\"",
						ctx->expression + prop->loc.l);
				return FAIL;
			}
		}
	}

	zbx_vector_eval_token_append_ptr(&ctx->stack, token);
	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds operand token to evaluation stack                            *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *             token - [IN] token to add                                      *
 *             error - [OUT]                                                  *
 *                                                                            *
 ******************************************************************************/
static int	eval_append_operand(zbx_eval_context_t *ctx, zbx_eval_token_t *token, char **error)
{
	if (0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
	{
		*error = zbx_dsprintf(*error, "operand following another operand at \"%s\"",
				ctx->expression + token->loc.l);
		return FAIL;
	}

	if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY))
	{
		int			i;
		zbx_eval_token_t	*prop = NULL;

		for (i = ctx->stack.values_num - 1; i >= 0; i--)
		{
			if (0 != (ctx->stack.values[i].type & ZBX_EVAL_CLASS_PROPERTY))
			{
				prop = &ctx->stack.values[i];
				continue;
			}

			if (0 == (ctx->stack.values[i].type & ZBX_EVAL_CLASS_OPERAND))
				break;
		}

		if (0 != (token->type & ZBX_EVAL_CLASS_PROPERTY))
		{
			if (NULL != prop)
			{
				*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
						" \"%s\"", ctx->expression + prop->loc.l);
				return FAIL;
			}
			prop = token;
		}

		if (NULL != prop && 2 < ctx->stack.values_num - i)
		{
			*error = zbx_dsprintf(*error, "property must be compared with a constant value at"
					" \"%s\"", ctx->expression + prop->loc.l);
			return FAIL;
		}
	}

	zbx_vector_eval_token_append_ptr(&ctx->stack, token);

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: adds null argument token to evaluation stack                      *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *                                                                            *
 ******************************************************************************/
static void	eval_append_arg_null(zbx_eval_context_t *ctx)
{
	zbx_eval_token_t	null_token = {.type = ZBX_EVAL_TOKEN_ARG_NULL};

	zbx_vector_eval_token_append_ptr(&ctx->stack, &null_token);
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees resources allocated by evaluation context                   *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *                                                                            *
 ******************************************************************************/
static void	eval_clear(zbx_eval_context_t *ctx)
{
	if (NULL != ctx->stack.values)
	{
		int	i;

		for (i = 0; i < ctx->stack.values_num; i++)
			zbx_variant_clear(&ctx->stack.values[i].value);

		zbx_vector_eval_token_destroy(&ctx->stack);
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses expression into tokens in postfix notation order           *
 *                                                                            *
 * Parameters: ctx        - [OUT] evaluation context                          *
 *             expression - [IN] expression to parse                          *
 *             rules      - [IN] parsing rules                                *
 *             error      - [OUT] error message in case of failure            *
 *                                                                            *
 * Return value: SUCCEED - expression was parsed successfully                 *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	eval_parse_expression(zbx_eval_context_t *ctx, const char *expression, zbx_uint64_t rules, char **error)
{
	size_t			pos = 0;
	int			ret = FAIL;
	zbx_eval_token_t	*optoken;

	ctx->expression = expression;
	ctx->rules = rules;
	ctx->last_token_type = ZBX_EVAL_CLASS_SEPARATOR;
	ctx->const_index = 0;
	ctx->functionid_index = 0;
	zbx_vector_eval_token_create(&ctx->stack);
	zbx_vector_eval_token_reserve(&ctx->stack, 16);
	zbx_vector_eval_token_create(&ctx->ops);
	zbx_vector_eval_token_reserve(&ctx->ops, 16);

	while ('\0' != expression[pos])
	{
		zbx_eval_token_t	token = {0};

		if (SUCCEED != eval_parse_token(ctx, pos, &token, error))
			goto out;

		if (0 == token.type)
			break;

		/* serialization used for parsed expression caching has limits expression to 0x7fffffff */
		if ((zbx_uint32_t)0x7fffffff < token.loc.r)
		{
			*error = zbx_strdup(*error, "too long expression");
			goto out;
		}

		if (ZBX_EVAL_TOKEN_ARG_QUERY == ctx->last_token_type && ZBX_EVAL_TOKEN_COMMA != token.type &&
				ZBX_EVAL_TOKEN_GROUP_CLOSE != token.type)
		{
			*error = zbx_dsprintf(*error, "invalid expression following query token at \"%s\"",
					ctx->expression + pos);
			goto out;
		}

		if (ZBX_EVAL_TOKEN_GROUP_CLOSE == token.type && ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type)
			eval_append_arg_null(ctx);

		if (ZBX_EVAL_TOKEN_GROUP_OPEN == token.type)
		{
			if (0 == (ctx->last_token_type & (ZBX_EVAL_BEFORE_OPERAND | ZBX_EVAL_CLASS_FUNCTION)))
			{
				*error = zbx_dsprintf(*error, "opening parenthesis must follow operator or function"
						" at \"%s\"", ctx->expression + pos);
				goto out;
			}

			zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
		}
		else if (0 != (token.type & ZBX_EVAL_CLASS_FUNCTION))
		{
			if (0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
			{
				*error = zbx_dsprintf(*error, "function must follow operand or unary operator"
						" at \"%s\"", ctx->expression + pos);
				goto out;
			}
			zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
		}
		else if (ZBX_EVAL_TOKEN_COMMA == token.type)
		{
			/* comma must follow and operand, comma or function */
			if (0 == (ctx->last_token_type & ZBX_EVAL_CLASS_OPERAND) &&
					(0 == (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR)))
			{
				*error = zbx_dsprintf(*error, "comma must follow an operand or separator at \"%s\"",
						ctx->expression + pos);
				goto out;
			}

			if (0 != (ctx->last_token_type & ZBX_EVAL_CLASS_SEPARATOR))
				eval_append_arg_null(ctx);

			for (optoken = NULL; 0 < ctx->ops.values_num; ctx->ops.values_num--)
			{
				optoken = &ctx->ops.values[ctx->ops.values_num - 1];

				if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
					break;

				if (FAIL == eval_append_operator(ctx, optoken, error))
					goto out;
			}

			if (NULL == optoken)
			{
				*error = zbx_dsprintf(*error, "missing function argument separator for comma at\"%s\"",
						ctx->expression + pos);
				goto out;
			}
		}
		else if (ZBX_EVAL_TOKEN_GROUP_CLOSE == token.type)
		{
			/* right parenthesis must follow and operand, right parenthesis or function */
			if (0 == (ctx->last_token_type & (ZBX_EVAL_CLASS_OPERAND | ZBX_EVAL_CLASS_PROPERTY |
					ZBX_EVAL_CLASS_SEPARATOR)) &&
					(ctx->ops.values_num < 2 ||
					ZBX_EVAL_TOKEN_FUNCTION != ctx->ops.values[ctx->ops.values_num - 2].type))
			{
				*error = zbx_dsprintf(*error, "right parenthesis must follow an operand or left"
						" parenthesis at \"%s\"", ctx->expression + pos);
				goto out;
			}

			for (optoken = NULL; 0 < ctx->ops.values_num; ctx->ops.values_num--)
			{
				optoken = &ctx->ops.values[ctx->ops.values_num - 1];

				if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
				{
					ctx->ops.values_num--;
					break;
				}

				if (FAIL == eval_append_operator(ctx, optoken, error))
					goto out;
			}

			if (NULL == optoken)
			{
				*error = zbx_dsprintf(*error, "missing left parenthesis for right parenthesis"
						" at \"%s\"", ctx->expression + pos);
				goto out;
			}

			if (0 != ctx->ops.values_num)
			{
				optoken = &ctx->ops.values[ctx->ops.values_num - 1];

				if (0 != (optoken->type & ZBX_EVAL_CLASS_FUNCTION))
				{
					if (FAIL == eval_append_operator(ctx, optoken, error))
						goto out;
					ctx->ops.values_num--;
				}
				else if (ZBX_EVAL_TOKEN_GROUP_OPEN == ctx->last_token_type)
				{
					*error = zbx_dsprintf(*error, "parenthesis cannot close empty group at \"%s\"",
							ctx->expression + pos);
					goto out;
				}
			}
		}
		else if (0 != (token.type & (ZBX_EVAL_CLASS_OPERAND | ZBX_EVAL_CLASS_PROPERTY)))
		{
			if (FAIL == eval_append_operand(ctx, &token, error))
				goto out;
		}
		else if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR))
		{
			/* binary operator cannot be used after operator */
			if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR2) &&
					0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERATOR))
			{
				*error = zbx_dsprintf(*error, "binary operator must be used after operand at \"%s\"",
						ctx->expression + pos);
				goto out;
			}

			/* unary !,- operators cannot follow an operand */
			if (0 != (token.type & ZBX_EVAL_CLASS_OPERATOR1) &&
					0 == (ctx->last_token_type & ZBX_EVAL_BEFORE_OPERAND))
			{
				*error = zbx_dsprintf(*error, "unary operator cannot follow an operand at \"%s\"",
						ctx->expression + pos);
				goto out;
			}

			for (; 0 < ctx->ops.values_num; ctx->ops.values_num--)
			{
				optoken = &ctx->ops.values[ctx->ops.values_num - 1];

				if ((optoken->type & ZBX_EVAL_OP_PRIORITY) > (token.type & ZBX_EVAL_OP_PRIORITY) ||
						0 != (token.type & ZBX_EVAL_CLASS_OPERATOR1))
					break;

				if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
					break;

				if (FAIL == eval_append_operator(ctx, optoken, error))
					goto out;
			}

			zbx_vector_eval_token_append_ptr(&ctx->ops, &token);
		}

		ctx->last_token_type = token.type;
		pos = token.loc.r + 1;
	}

	if (0 != (ctx->last_token_type & ZBX_EVAL_CLASS_OPERATOR))
	{
		*error = zbx_strdup(*error, "expression ends with operator");
		goto out;
	}

	if (ZBX_EVAL_TOKEN_COMMA == ctx->last_token_type)
	{
		*error = zbx_strdup(*error, "expression ends with comma");
		goto out;
	}

	for (; 0 < ctx->ops.values_num; ctx->ops.values_num--)
	{
		optoken = &ctx->ops.values[ctx->ops.values_num - 1];

		if (ZBX_EVAL_TOKEN_GROUP_OPEN == optoken->type)
		{
			*error = zbx_dsprintf(*error, "mismatched () brackets in expression: %s", ctx->expression);
			goto out;
		}

		if (FAIL == eval_append_operator(ctx, optoken, error))
			goto out;
	}

	if (0 == ctx->stack.values_num)
	{
		*error = zbx_strdup(*error, "empty expression");
		goto out;
	}

	if (0 != (ctx->rules & ZBX_EVAL_PARSE_PROPERTY) && 1 == ctx->stack.values_num)
	{
		if (0 != (ctx->stack.values[ctx->stack.values_num - 1].type & ZBX_EVAL_CLASS_PROPERTY))
		{
			zbx_eval_token_t	*prop = &ctx->stack.values[ctx->stack.values_num - 1];

			*error = zbx_dsprintf(*error, "missing comparison string for property at \"%s\"",
						ctx->expression + prop->loc.l);
			goto out;
		}
	}

	ret = SUCCEED;
out:
	zbx_vector_eval_token_destroy(&ctx->ops);

	if (SUCCEED != ret)
		eval_clear(ctx);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: parses expression into tokens in postfix notation order           *
 *                                                                            *
 * Parameters: ctx        - [OUT] evaluation context                          *
 *             expression - [IN] expression to parse                          *
 *             rules      - [IN] parsing rules                                *
 *             error      - [OUT] error message in case of failure            *
 *                                                                            *
 * Return value: SUCCEED - expression was parsed successfully                 *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_eval_parse_expression(zbx_eval_context_t *ctx, const char *expression, zbx_uint64_t rules, char **error)
{
	return eval_parse_expression(ctx, expression, rules, error);
}

/******************************************************************************
 *                                                                            *
 * Purpose: initializes context so it can be cleared without parsing          *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *                                                                            *
 ******************************************************************************/
void	zbx_eval_init(zbx_eval_context_t *ctx)
{
	memset(ctx, 0, sizeof(zbx_eval_context_t));
}

/******************************************************************************
 *                                                                            *
 * Purpose: frees resources allocated by evaluation context                   *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *                                                                            *
 ******************************************************************************/
void	zbx_eval_clear(zbx_eval_context_t *ctx)
{
	eval_clear(ctx);
}

/******************************************************************************
 *                                                                            *
 * Purpose: returns evaluation context status                                 *
 *                                                                            *
 * Parameters: ctx   - [IN] evaluation context                                *
 *                                                                            *
 * Return value: SUCCEED - contains parsed expression                         *
 *               FAIL    - Empty, either parsing failed or was initialized    *
 *                         without parsing.                                   *
 *                                                                            *
 ******************************************************************************/
int	zbx_eval_status(const zbx_eval_context_t *ctx)
{
	return (NULL == ctx->expression ? FAIL : SUCCEED);
}