1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-25 20:36:38 +02:00
Files
weechat/src/core/wee-eval.c
T

867 lines
25 KiB
C

/*
* wee-eval.c - evaluate expressions with references to internal vars
*
* Copyright (C) 2012-2014 Sébastien Helleu <flashcode@flashtux.org>
*
* This file is part of WeeChat, the extensible chat client.
*
* WeeChat is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* WeeChat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WeeChat. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include "weechat.h"
#include "wee-eval.h"
#include "wee-config-file.h"
#include "wee-hashtable.h"
#include "wee-hdata.h"
#include "wee-hook.h"
#include "wee-secure.h"
#include "wee-string.h"
#include "../gui/gui-buffer.h"
#include "../gui/gui-color.h"
#include "../gui/gui-window.h"
#include "../plugins/plugin.h"
char *logical_ops[EVAL_NUM_LOGICAL_OPS] = { "||", "&&" };
char *comparisons[EVAL_NUM_COMPARISONS] = { "==", "!=", "<=", "<", ">=", ">",
"=~", "!~" };
struct t_hashtable *eval_hashtable_pointers = NULL;
/*
* Checks if a value is true: a value is true if string is non-NULL, non-empty
* and different from "0".
*
* Returns:
* 1: value is true
* 0: value is false
*/
int
eval_is_true (const char *value)
{
return (value && value[0] && (strcmp (value, "0") != 0)) ? 1 : 0;
}
/*
* Gets value of hdata using "path" to a variable.
*
* Note: result must be freed after use.
*/
char *
eval_hdata_get_value (struct t_hdata *hdata, void *pointer, const char *path)
{
char *value, *old_value, *var_name, str_value[128], *pos;
const char *ptr_value, *hdata_name;
int type;
struct t_hashtable *hashtable;
value = NULL;
var_name = NULL;
/* NULL pointer? return empty string */
if (!pointer)
return strdup ("");
/* no path? just return current pointer as string */
if (!path || !path[0])
{
snprintf (str_value, sizeof (str_value),
"0x%lx", (long unsigned int)pointer);
return strdup (str_value);
}
/*
* look for name of hdata, for example in "window.buffer.full_name", the
* hdata name is "window"
*/
pos = strchr (path, '.');
if (pos > path)
var_name = string_strndup (path, pos - path);
else
var_name = strdup (path);
if (!var_name)
goto end;
/* search type of variable in hdata */
type = hdata_get_var_type (hdata, var_name);
if (type < 0)
goto end;
/* build a string with the value or variable */
switch (type)
{
case WEECHAT_HDATA_CHAR:
snprintf (str_value, sizeof (str_value),
"%c", hdata_char (hdata, pointer, var_name));
value = strdup (str_value);
break;
case WEECHAT_HDATA_INTEGER:
snprintf (str_value, sizeof (str_value),
"%d", hdata_integer (hdata, pointer, var_name));
value = strdup (str_value);
break;
case WEECHAT_HDATA_LONG:
snprintf (str_value, sizeof (str_value),
"%ld", hdata_long (hdata, pointer, var_name));
value = strdup (str_value);
break;
case WEECHAT_HDATA_STRING:
case WEECHAT_HDATA_SHARED_STRING:
ptr_value = hdata_string (hdata, pointer, var_name);
value = (ptr_value) ? strdup (ptr_value) : NULL;
break;
case WEECHAT_HDATA_POINTER:
pointer = hdata_pointer (hdata, pointer, var_name);
snprintf (str_value, sizeof (str_value),
"0x%lx", (long unsigned int)pointer);
value = strdup (str_value);
break;
case WEECHAT_HDATA_TIME:
snprintf (str_value, sizeof (str_value),
"%ld", (long)hdata_time (hdata, pointer, var_name));
value = strdup (str_value);
break;
case WEECHAT_HDATA_HASHTABLE:
pointer = hdata_hashtable (hdata, pointer, var_name);
if (pos)
{
/*
* for a hashtable, if there is a "." after name of hdata,
* get the value for this key in hashtable
*/
hashtable = pointer;
ptr_value = hashtable_get (hashtable, pos + 1);
if (ptr_value)
{
switch (hashtable->type_values)
{
case HASHTABLE_INTEGER:
snprintf (str_value, sizeof (str_value),
"%d", *((int *)ptr_value));
value = strdup (str_value);
break;
case HASHTABLE_STRING:
value = strdup (ptr_value);
break;
case HASHTABLE_POINTER:
case HASHTABLE_BUFFER:
snprintf (str_value, sizeof (str_value),
"0x%lx", (long unsigned int)ptr_value);
value = strdup (str_value);
break;
case HASHTABLE_TIME:
snprintf (str_value, sizeof (str_value),
"%ld", (long)(*((time_t *)ptr_value)));
value = strdup (str_value);
break;
case HASHTABLE_NUM_TYPES:
break;
}
}
}
else
{
snprintf (str_value, sizeof (str_value),
"0x%lx", (long unsigned int)pointer);
value = strdup (str_value);
}
break;
}
/*
* if we are on a pointer and that something else is in path (after "."),
* go on with this pointer and remaining path
*/
if ((type == WEECHAT_HDATA_POINTER) && pos)
{
hdata_name = hdata_get_var_hdata (hdata, var_name);
if (!hdata_name)
goto end;
hdata = hook_hdata_get (NULL, hdata_name);
old_value = value;
value = eval_hdata_get_value (hdata, pointer, (pos) ? pos + 1 : NULL);
if (old_value)
free (old_value);
}
end:
if (var_name)
free (var_name);
return value;
}
/*
* Replaces variables, which can be, by order of priority:
* 1. an extra variable (from hashtable "extra_vars")
* 2. a color (format: color:xxx)
* 3. an option (format: file.section.option)
* 4. a buffer local variable
* 5. a hdata name/variable
*
* Examples:
* option: ${weechat.look.scroll_amount}
* hdata : ${window.buffer.full_name}
* ${window.buffer.local_variables.type}
*/
char *
eval_replace_vars_cb (void *data, const char *text)
{
struct t_hashtable *pointers, *extra_vars;
struct t_config_option *ptr_option;
struct t_gui_buffer *ptr_buffer;
char str_value[64], *value, *pos, *pos1, *pos2, *hdata_name, *list_name;
char *tmp, *info_name;
const char *ptr_value, *ptr_arguments;
struct t_hdata *hdata;
void *pointer;
pointers = (struct t_hashtable *)(((void **)data)[0]);
extra_vars = (struct t_hashtable *)(((void **)data)[1]);
/* 1. look for var in hashtable "extra_vars" */
if (extra_vars)
{
ptr_value = hashtable_get (extra_vars, text);
if (ptr_value)
return strdup (ptr_value);
}
/* 2. look for a color */
if (strncmp (text, "color:", 6) == 0)
{
ptr_value = gui_color_get_custom (text + 6);
return strdup ((ptr_value) ? ptr_value : "");
}
/* 3. look for an info */
if (strncmp (text, "info:", 5) == 0)
{
ptr_value = NULL;
ptr_arguments = strchr (text + 5, ',');
if (ptr_arguments)
{
info_name = string_strndup (text + 5, ptr_arguments - text - 5);
ptr_arguments++;
}
else
info_name = strdup (text + 5);
if (info_name)
{
ptr_value = hook_info_get (NULL, info_name, ptr_arguments);
free (info_name);
}
return strdup ((ptr_value) ? ptr_value : "");
}
/* 4. look for name of option: if found, return this value */
if (strncmp (text, "sec.data.", 9) == 0)
{
ptr_value = hashtable_get (secure_hashtable_data, text + 9);
return strdup ((ptr_value) ? ptr_value : "");
}
else
{
config_file_search_with_string (text, NULL, NULL, &ptr_option, NULL);
if (ptr_option)
{
switch (ptr_option->type)
{
case CONFIG_OPTION_TYPE_BOOLEAN:
return strdup (CONFIG_BOOLEAN(ptr_option) ? EVAL_STR_TRUE : EVAL_STR_FALSE);
case CONFIG_OPTION_TYPE_INTEGER:
if (ptr_option->string_values)
return strdup (ptr_option->string_values[CONFIG_INTEGER(ptr_option)]);
snprintf (str_value, sizeof (str_value),
"%d", CONFIG_INTEGER(ptr_option));
return strdup (str_value);
case CONFIG_OPTION_TYPE_STRING:
return strdup (CONFIG_STRING(ptr_option));
case CONFIG_OPTION_TYPE_COLOR:
return strdup (gui_color_get_name (CONFIG_COLOR(ptr_option)));
case CONFIG_NUM_OPTION_TYPES:
return NULL;
}
}
}
/* 5. look for local variable in buffer */
ptr_buffer = hashtable_get (pointers, "buffer");
if (ptr_buffer)
{
ptr_value = hashtable_get (ptr_buffer->local_variables, text);
if (ptr_value)
return strdup (ptr_value);
}
/* 6. look for hdata */
value = NULL;
hdata_name = NULL;
list_name = NULL;
pointer = NULL;
pos = strchr (text, '.');
if (pos > text)
hdata_name = string_strndup (text, pos - text);
else
hdata_name = strdup (text);
if (!hdata_name)
goto end;
pos1 = strchr (hdata_name, '[');
if (pos1 > hdata_name)
{
pos2 = strchr (pos1 + 1, ']');
if (pos2 > pos1 + 1)
{
list_name = string_strndup (pos1 + 1, pos2 - pos1 - 1);
}
tmp = string_strndup (hdata_name, pos1 - hdata_name);
if (tmp)
{
free (hdata_name);
hdata_name = tmp;
}
}
hdata = hook_hdata_get (NULL, hdata_name);
if (!hdata)
goto end;
if (list_name)
pointer = hdata_get_list (hdata, list_name);
if (!pointer)
{
pointer = hashtable_get (pointers, hdata_name);
if (!pointer)
goto end;
}
value = eval_hdata_get_value (hdata, pointer, (pos) ? pos + 1 : NULL);
end:
if (hdata_name)
free (hdata_name);
if (list_name)
free (list_name);
return (value) ? value : strdup ("");
}
/*
* Replaces variables in a string.
*
* Note: result must be freed after use.
*/
char *
eval_replace_vars (const char *expr, struct t_hashtable *pointers,
struct t_hashtable *extra_vars,
const char *prefix, const char *suffix)
{
int errors;
void *ptr[2];
ptr[0] = pointers;
ptr[1] = extra_vars;
return string_replace_with_callback (expr, prefix, suffix,
&eval_replace_vars_cb,
ptr,
&errors);
}
/*
* Compares two expressions.
*
* Returns:
* "1": comparison is true
* "0": comparison is false
*
* Examples:
* "15 > 2": returns "1"
* "abc == def": returns "0"
*
* Note: result must be freed after use.
*/
char *
eval_compare (const char *expr1, int comparison, const char *expr2)
{
int rc, string_compare, length1, length2;
regex_t regex;
long value1, value2;
char *error;
rc = 0;
string_compare = 0;
if (!expr1 || !expr2)
goto end;
if ((comparison == EVAL_COMPARE_REGEX_MATCHING)
|| (comparison == EVAL_COMPARE_REGEX_NOT_MATCHING))
{
if (string_regcomp (&regex, expr2,
REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0)
{
goto end;
}
rc = (regexec (&regex, expr1, 0, NULL, 0) == 0) ? 1 : 0;
if (comparison == EVAL_COMPARE_REGEX_NOT_MATCHING)
rc ^= 1;
goto end;
}
length1 = strlen (expr1);
length2 = strlen (expr2);
/*
* string comparison is forced if expr1 and expr2 have double quotes at
* beginning/end
*/
if (((length1 == 0) || ((expr1[0] == '"') && expr1[length1 - 1] == '"'))
&& ((length2 == 0) || ((expr2[0] == '"') && expr2[length2 - 1] == '"')))
{
string_compare = 1;
}
if (!string_compare)
{
value1 = strtol (expr1, &error, 10);
if (!error || error[0])
string_compare = 1;
else
{
value2 = strtol (expr2, &error, 10);
if (!error || error[0])
string_compare = 1;
}
}
if (string_compare)
rc = strcmp (expr1, expr2);
else
rc = (value1 < value2) ? -1 : ((value1 > value2) ? 1 : 0);
switch (comparison)
{
case EVAL_COMPARE_EQUAL:
rc = (rc == 0);
break;
case EVAL_COMPARE_NOT_EQUAL:
rc = (rc != 0);
break;
case EVAL_COMPARE_LESS_EQUAL:
rc = (rc <= 0);
break;
case EVAL_COMPARE_LESS:
rc = (rc < 0);
break;
case EVAL_COMPARE_GREATER_EQUAL:
rc = (rc >= 0);
break;
case EVAL_COMPARE_GREATER:
rc = (rc > 0);
break;
case EVAL_NUM_COMPARISONS:
break;
}
end:
return strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE);
}
/*
* Evaluates a condition (this function must not be called directly).
*
* Argument keep_parentheses is almost always 0, it is 1 only if the expression
* is a regex (to keep flags inside the parentheses).
*
* For return value, see function eval_expression().
*
* Note: result must be freed after use (if not NULL).
*/
char *
eval_expression_condition (const char *expr, struct t_hashtable *pointers,
struct t_hashtable *extra_vars,
int keep_parentheses,
const char *prefix, const char *suffix)
{
int logic, comp, length, level, rc;
const char *pos_end;
char *expr2, *sub_expr, *pos, *pos2, *value, *tmp_value, *tmp_value2;
value = NULL;
if (!expr)
return NULL;
if (!expr[0])
return strdup (expr);
/* skip spaces at beginning of string */
while (expr[0] == ' ')
{
expr++;
}
if (!expr[0])
return strdup (expr);
/* skip spaces at end of string */
pos_end = expr + strlen (expr) - 1;
while ((pos_end > expr) && (pos_end[0] == ' '))
{
pos_end--;
}
expr2 = string_strndup (expr, pos_end + 1 - expr);
if (!expr2)
return NULL;
/*
* evaluate sub-expressions between parentheses and replace them with their
* value
*/
if (!keep_parentheses)
{
while ((pos = strchr (expr2, '(')) != NULL)
{
level = 0;
pos2 = pos + 1;
while (pos2[0])
{
if (pos2[0] == '(')
level++;
else if (pos2[0] == ')')
{
if (level == 0)
break;
level--;
}
pos2++;
}
/* closing parenthesis not found */
if (pos2[0] != ')')
goto end;
sub_expr = string_strndup (pos + 1, pos2 - pos - 1);
if (!sub_expr)
goto end;
tmp_value = eval_expression_condition (sub_expr, pointers,
extra_vars,
0, prefix, suffix);
free (sub_expr);
if (!pos[1])
{
/* nothing around parentheses, then return value of sub-expression as-is */
value = tmp_value;
goto end;
}
/*
* build a string with string before '(' +
* result of sub-expression + string after ')'
*/
length = (pos - expr2) + 1
+ ((tmp_value) ? strlen (tmp_value) : 0)
+ 1 + strlen (pos2 + 1)
+ 1;
tmp_value2 = malloc (length);
if (!tmp_value2)
goto end;
tmp_value2[0] = '\0';
if (pos > expr2)
{
strncat (tmp_value2, expr2, pos - expr2);
strcat (tmp_value2, " ");
}
if (tmp_value)
strcat (tmp_value2, tmp_value);
if (pos2[1])
{
strcat (tmp_value2, " ");
strcat (tmp_value2, pos2 + 1);
}
free (expr2);
expr2 = tmp_value2;
}
}
/*
* search for a logical operator, and if one is found:
* - split expression into two sub-expressions
* - evaluate first sub-expression
* - if needed, evaluate second sub-expression
* - return result
*/
for (logic = 0; logic < EVAL_NUM_LOGICAL_OPS; logic++)
{
pos = strstr (expr2, logical_ops[logic]);
if (pos > expr2)
{
pos_end = pos - 1;
while ((pos_end > expr2) && (pos_end[0] == ' '))
{
pos_end--;
}
sub_expr = string_strndup (expr2, pos_end + 1 - expr2);
if (!sub_expr)
goto end;
tmp_value = eval_expression_condition (sub_expr, pointers,
extra_vars,
0, prefix, suffix);
free (sub_expr);
rc = eval_is_true (tmp_value);
if (tmp_value)
free (tmp_value);
/*
* if rc == 0 with "&&" or rc == 1 with "||", no need to evaluate
* second sub-expression, just return the rc
*/
if ((!rc && (logic == EVAL_LOGICAL_OP_AND))
|| (rc && (logic == EVAL_LOGICAL_OP_OR)))
{
value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE);
goto end;
}
pos += strlen (logical_ops[logic]);
while (pos[0] == ' ')
{
pos++;
}
tmp_value = eval_expression_condition (pos, pointers, extra_vars,
0, prefix, suffix);
rc = eval_is_true (tmp_value);
if (tmp_value)
free (tmp_value);
value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE);
goto end;
}
}
/*
* search for a comparison, and if one is found:
* - split expression into two sub-expressions
* - evaluate the two sub-expressions
* - compare sub-expressions
* - return result
*/
for (comp = 0; comp < EVAL_NUM_COMPARISONS; comp++)
{
pos = strstr (expr2, comparisons[comp]);
if (pos > expr2)
{
pos_end = pos - 1;
while ((pos_end > expr2) && (pos_end[0] == ' '))
{
pos_end--;
}
sub_expr = string_strndup (expr2, pos_end + 1 - expr2);
if (!sub_expr)
goto end;
tmp_value = eval_expression_condition (sub_expr, pointers,
extra_vars,
0, prefix, suffix);
free (sub_expr);
pos += strlen (comparisons[comp]);
while (pos[0] == ' ')
{
pos++;
}
tmp_value2 = eval_expression_condition (pos, pointers, extra_vars,
((comp == EVAL_COMPARE_REGEX_MATCHING)
|| (comp == EVAL_COMPARE_REGEX_NOT_MATCHING)) ? 1 : 0,
prefix, suffix);
value = eval_compare (tmp_value, comp, tmp_value2);
if (tmp_value)
free (tmp_value);
if (tmp_value2)
free (tmp_value2);
goto end;
}
}
/*
* at this point, there is no more logical operator neither comparison,
* so we just replace variables in string and return the result
*/
value = eval_replace_vars (expr2, pointers, extra_vars, prefix, suffix);
end:
if (expr2)
free (expr2);
return value;
}
/*
* Evaluates an expression.
*
* The hashtable "pointers" must have string for keys, pointer for values.
* The hashtable "extra_vars" must have string for keys and values.
* The hashtable "options" must have string for keys and values.
*
* Supported options:
* - prefix: change the default prefix before variables to replace ("${")
* - suffix: change the default suffix after variables to replace ('}")
* - type:
* - condition: evaluate as a condition (use operators/parentheses,
* return a boolean)
*
* If the expression is a condition, it can contain:
* - conditions: == != < <= > >=
* - logical operators: && ||
* - parentheses for priority
*
* Examples of simple expression without condition (the [ ] are NOT part of
* result):
* >> ${window.buffer.number}
* == [2]
* >> buffer:${window.buffer.full_name}
* == [buffer:irc.freenode.#weechat]
* >> ${window.win_width}
* == [112]
* >> ${window.win_height}
* == [40]
*
* Examples of conditions:
* >> ${window.buffer.full_name} == irc.freenode.#weechat
* == [1]
* >> ${window.buffer.full_name} == irc.freenode.#test
* == [0]
* >> ${window.win_width} >= 30 && ${window.win_height} >= 20
* == [1]
*
* Note: result must be freed after use (if not NULL).
*/
char *
eval_expression (const char *expr, struct t_hashtable *pointers,
struct t_hashtable *extra_vars, struct t_hashtable *options)
{
int condition, rc;
char *value;
const char *prefix, *suffix, *default_prefix = "${", *default_suffix = "}";
const char *ptr_value;
struct t_gui_window *window;
if (!expr)
return NULL;
condition = 0;
prefix = default_prefix;
suffix = default_suffix;
/* create hashtable pointers if it's NULL */
if (!pointers)
{
if (eval_hashtable_pointers)
hashtable_remove_all (eval_hashtable_pointers);
else
{
eval_hashtable_pointers = hashtable_new (32,
WEECHAT_HASHTABLE_STRING,
WEECHAT_HASHTABLE_POINTER,
NULL,
NULL);
if (!eval_hashtable_pointers)
return NULL;
}
pointers = eval_hashtable_pointers;
}
/*
* set window/buffer with pointer to current window/buffer
* (if not already defined in the hashtable)
*/
if (gui_current_window)
{
if (!hashtable_has_key (pointers, "window"))
hashtable_set (pointers, "window", gui_current_window);
if (!hashtable_has_key (pointers, "buffer"))
{
window = (struct t_gui_window *)hashtable_get (pointers, "window");
if (window)
hashtable_set (pointers, "buffer", window->buffer);
}
}
/* read options */
if (options)
{
/* check the type of evaluation */
ptr_value = hashtable_get (options, "type");
if (ptr_value && (strcmp (ptr_value, "condition") == 0))
condition = 1;
/* check for custom prefix */
ptr_value = hashtable_get (options, "prefix");
if (ptr_value && ptr_value[0])
prefix = ptr_value;
/* check for custom suffix */
ptr_value = hashtable_get (options, "suffix");
if (ptr_value && ptr_value[0])
suffix = ptr_value;
}
/* evaluate expression */
if (condition)
{
/* evaluate as condition (return a boolean: "0" or "1") */
value = eval_expression_condition (expr, pointers, extra_vars,
0, prefix, suffix);
rc = eval_is_true (value);
if (value)
free (value);
value = strdup ((rc) ? EVAL_STR_TRUE : EVAL_STR_FALSE);
}
else
{
/* only replace variables in expression */
value = eval_replace_vars (expr, pointers, extra_vars, prefix, suffix);
}
return value;
}
/*
* Frees all allocated data.
*/
void
eval_end ()
{
if (eval_hashtable_pointers)
{
hashtable_free (eval_hashtable_pointers);
eval_hashtable_pointers = NULL;
}
}