diff options
author | Sébastien Helleu <flashcode@flashtux.org> | 2019-09-20 21:37:01 +0200 |
---|---|---|
committer | Sébastien Helleu <flashcode@flashtux.org> | 2019-09-20 21:37:01 +0200 |
commit | 997894edc04e87b0ba0c81712c92a83ec7f5c121 (patch) | |
tree | 8dc259f773da21c8592421da0323694843b146db /src/core | |
parent | 0109c519375f8a54552127e71ea2b00b1baa296e (diff) | |
download | weechat-997894edc04e87b0ba0c81712c92a83ec7f5c121.zip |
core: add calculation of expression in evaluation of expressions with "calc:..." (issue #997)
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/Makefile.am | 2 | ||||
-rw-r--r-- | src/core/wee-calc.c | 355 | ||||
-rw-r--r-- | src/core/wee-calc.h | 25 | ||||
-rw-r--r-- | src/core/wee-command.c | 9 | ||||
-rw-r--r-- | src/core/wee-eval.c | 23 |
6 files changed, 406 insertions, 9 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 55c1e1112..b37a5522e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -23,6 +23,7 @@ set(LIB_CORE_SRC weechat.c weechat.h wee-arraylist.c wee-arraylist.h wee-backtrace.c wee-backtrace.h + wee-calc.c wee-calc.h wee-command.c wee-command.h wee-completion.c wee-completion.h wee-config.c wee-config.h diff --git a/src/core/Makefile.am b/src/core/Makefile.am index df03ed39a..82f364b32 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -27,6 +27,8 @@ lib_weechat_core_a_SOURCES = weechat.c \ wee-arraylist.h \ wee-backtrace.c \ wee-backtrace.h \ + wee-calc.c \ + wee-calc.h \ wee-command.c \ wee-command.h \ wee-completion.c \ diff --git a/src/core/wee-calc.c b/src/core/wee-calc.c new file mode 100644 index 000000000..80797d775 --- /dev/null +++ b/src/core/wee-calc.c @@ -0,0 +1,355 @@ +/* + * wee-calc.c - calculate result of an expression + * + * Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <math.h> +#include <locale.h> + +#include "weechat.h" +#include "wee-arraylist.h" +#include "wee-string.h" + + +/* + * Callback called to free a value or op in the arraylist. + */ + +void +calc_list_free_cb (void *data, struct t_arraylist *arraylist, void *pointer) +{ + /* make C compiler happy */ + (void) data; + (void) arraylist; + + free (pointer); +} + +/* + * Returns the precedence of an operator: + * - '*' and '/': 2 + * - '+' and '-': 1 + * - any other: 0 + */ + +int +calc_operator_precedence (char *operator) +{ + if (!operator) + return 0; + + if ((strcmp (operator, "*") == 0) + || (strcmp (operator, "/") == 0) + || (strcmp (operator, "//") == 0) + || (strcmp (operator, "%") == 0)) + { + return 2; + } + + if ((strcmp (operator, "+") == 0) + || (strcmp (operator, "-") == 0)) + { + return 1; + } + + return 0; +} + +/* + * Pops an integer value from the stack of values. + */ + +double +calc_pop_value (struct t_arraylist *list_values) +{ + int size_values; + double *ptr_value, value; + + size_values = arraylist_size (list_values); + + if (size_values < 1) + return 0; + + ptr_value = arraylist_get (list_values, size_values - 1); + value = *ptr_value; + + arraylist_remove (list_values, size_values - 1); + + return value; +} + +/* + * Calculates result of an operation using an operator and two values. + */ + +double +calc_operation (char *operator, double value1, double value2) +{ + if (strcmp (operator, "+") == 0) + return value1 + value2; + + if (strcmp (operator, "-") == 0) + return value1 - value2; + + if (strcmp (operator, "*") == 0) + return value1 * value2; + + if (strcmp (operator, "/") == 0) + return (value2 != 0) ? value1 / value2 : 0; + + if (strcmp (operator, "//") == 0) + return (value2 != 0) ? floor (value1 / value2) : 0; + + if (strcmp (operator, "%") == 0) + return (value2 != 0) ? fmod (value1, value2) : 0; + + return 0; +} + +/* + * Calculates result of an operation using the operator on the operators stack + * and the two values on the values stack. + * + * The result is pushed on values stack. + */ + +void +calc_operation_stacks (struct t_arraylist *list_values, + struct t_arraylist *list_ops) +{ + int size_ops; + double value1, value2, result, *ptr_result; + char *ptr_operator; + + size_ops = arraylist_size (list_ops); + if (size_ops < 1) + return; + + ptr_operator = arraylist_get (list_ops, size_ops - 1); + + value2 = calc_pop_value (list_values); + value1 = calc_pop_value (list_values); + + result = calc_operation (ptr_operator, value1, value2); + + ptr_result = malloc (sizeof (result)); + *ptr_result = result; + arraylist_add (list_values, ptr_result); + + arraylist_remove (list_ops, size_ops - 1); +} + +/* + * Formats the result as a decimal number (locale independent): remove any + * extra "0" at the and the decimal point if needed. + */ + +void +calc_format_result (double value, char *result, int max_size) +{ + char *pos_point; + int i; + + /* + * local-independent formatting of value, so that a decimal point is always + * used (instead of a comma in French for example) + */ + setlocale (LC_ALL, "C"); + snprintf (result, max_size, "%.10f", value); + setlocale (LC_ALL, ""); + + pos_point = strchr (result, '.'); + + i = strlen (result) - 1; + while (i >= 0) + { + if (!isdigit (result[i]) && (result[i] != '-')) + { + result[i] = '\0'; + break; + } + if (pos_point && (result[i] == '0')) + { + result[i] = '\0'; + i--; + } + else + break; + } +} + +/* + * Calculates an expression, which can contain: + * - integer and decimal numbers (ie 2 or 2.5) + * - operators: + * +: addition + * -: subtraction + * *: multiplication + * /: division + * \: division giving an integer as result + * %: remainder of division + * - parentheses: ( ) + * + * The value returned is a string representation of the result, which can be + * an integer or a double, according to the operations and numbers in input. + * + * Note: result must be freed after use (if not NULL). + */ + +char * +calc_expression (const char *expr) +{ + struct t_arraylist *list_values, *list_ops; + char str_result[64], *ptr_operator, *operator; + int i, i2, index_op, decimals; + double value, *ptr_value; + + list_values = NULL; + list_ops = NULL; + + /* return 0 by default in case of error */ + snprintf (str_result, sizeof (str_result), "0"); + + if (!expr) + goto end; + + /* stack with values */ + list_values = arraylist_new (32, 0, 1, + NULL, NULL, + &calc_list_free_cb, NULL); + if (!list_values) + goto end; + + /* stack with operators */ + list_ops = arraylist_new (32, 0, 1, + NULL, NULL, + &calc_list_free_cb, NULL); + if (!list_ops) + goto end; + + for (i = 0; expr[i]; i++) + { + if (expr[i] == ' ') + { + /* ignore spaces */ + continue; + } + else if (expr[i] == '(') + { + ptr_operator = string_strndup (expr + i, 1); + arraylist_add (list_ops, ptr_operator); + } + else if (isdigit (expr[i]) || (expr[i] == '.')) + { + value = 0; + decimals = 0; + while (expr[i] && (isdigit (expr[i]) || (expr[i] == '.'))) + { + if (expr[i] == '.') + { + if (decimals == 0) + decimals = 10; + } + else + { + if (decimals) + { + value = value + (((double)(expr[i] - '0')) / decimals); + decimals *= 10; + } + else + { + value = (value * 10) + (expr[i] - '0'); + } + } + i++; + } + i--; + ptr_value = malloc (sizeof (value)); + *ptr_value = value; + arraylist_add (list_values, ptr_value); + } + else if (expr[i] == ')') + { + index_op = arraylist_size (list_ops) - 1; + while (index_op >= 0) + { + ptr_operator = arraylist_get (list_ops, index_op); + if (strcmp (ptr_operator, "(") == 0) + break; + calc_operation_stacks (list_values, list_ops); + index_op--; + } + /* remove "(" from operators */ + index_op = arraylist_size (list_ops) - 1; + if (index_op >= 0) + arraylist_remove (list_ops, index_op); + } + else + { + /* operator */ + i2 = i + 1; + while (expr[i2] && (expr[i2] != ' ') && (expr[i2] != '(') + && (expr[i2] != ')') && (expr[i2] != '.') + && !isdigit (expr[i2])) + { + i2++; + } + operator = string_strndup (expr + i, i2 - i); + i = i2 - 1; + if (operator) + { + index_op = arraylist_size (list_ops) - 1; + while (index_op >= 0) + { + ptr_operator = arraylist_get (list_ops, index_op); + if (calc_operator_precedence (ptr_operator) < + calc_operator_precedence (operator)) + break; + calc_operation_stacks (list_values, list_ops); + index_op--; + } + arraylist_add (list_ops, operator); + } + } + } + + while (arraylist_size (list_ops) > 0) + { + calc_operation_stacks (list_values, list_ops); + } + + value = calc_pop_value (list_values); + calc_format_result (value, str_result, sizeof (str_result)); + +end: + if (list_values) + arraylist_free (list_values); + if (list_ops) + arraylist_free (list_ops); + + return strdup (str_result); +} diff --git a/src/core/wee-calc.h b/src/core/wee-calc.h new file mode 100644 index 000000000..d4c308640 --- /dev/null +++ b/src/core/wee-calc.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef WEECHAT_CALC_H +#define WEECHAT_CALC_H + +extern char *calc_expression (const char *expr); + +#endif /* WEECHAT_CALC_H */ diff --git a/src/core/wee-command.c b/src/core/wee-command.c index 1f5debd96..d5d3846dd 100644 --- a/src/core/wee-command.c +++ b/src/core/wee-command.c @@ -7363,9 +7363,11 @@ command_init () " 10. an environment variable (format: \"env:XXX\")\n" " 11. a ternary operator (format: " "\"if:condition?value_if_true:value_if_false\")\n" - " 12. an option (format: \"file.section.option\")\n" - " 13. a local variable in buffer\n" - " 14. a hdata name/variable (the value is automatically converted " + " 12. result of an expression with parentheses and operators " + "+ - * / // % (format: \"calc:xxx\")\n" + " 13. an option (format: \"file.section.option\")\n" + " 14. a local variable in buffer\n" + " 15. a hdata name/variable (the value is automatically converted " "to string), by default \"window\" and \"buffer\" point to current " "window/buffer.\n" "Format for hdata can be one of following:\n" @@ -7401,6 +7403,7 @@ command_init () " /eval -n ${if:${info:term_width}>80?big:small} ==> big\n" " /eval -n ${rev:Hello} ==> olleH\n" " /eval -n ${repeat:5,-} ==> -----\n" + " /eval -n ${calc:(5+2)*3} ==> 21\n" "\n" "Examples (conditions):\n" " /eval -n -c ${window.buffer.number} > 2 ==> 0\n" diff --git a/src/core/wee-eval.c b/src/core/wee-eval.c index ab4ed94da..547a4406b 100644 --- a/src/core/wee-eval.c +++ b/src/core/wee-eval.c @@ -30,6 +30,7 @@ #include "weechat.h" #include "wee-eval.h" +#include "wee-calc.h" #include "wee-config-file.h" #include "wee-hashtable.h" #include "wee-hdata.h" @@ -301,9 +302,10 @@ end: * 11. current date/time (format: date or date:xxx) * 12. an environment variable (format: env:XXX) * 13. a ternary operator (format: if:condition?value_if_true:value_if_false) - * 14. an option (format: file.section.option) - * 15. a buffer local variable - * 16. a hdata variable (format: hdata.var1.var2 or hdata[list].var1.var2 + * 14. calculate result of an expression (format: calc:xxx) + * 15. an option (format: file.section.option) + * 16. a buffer local variable + * 17. a hdata variable (format: hdata.var1.var2 or hdata[list].var1.var2 * or hdata[ptr].var1.var2) * * See /help in WeeChat for examples. @@ -625,7 +627,16 @@ eval_replace_vars_cb (void *data, const char *text) return (value) ? value : strdup (""); } - /* 14. option: if found, return this value */ + /* + * 14. calculate the result of an expression + * (with number, operators and parentheses) + */ + if (strncmp (text, "calc:", 5) == 0) + { + return calc_expression (text + 5); + } + + /* 15. option: if found, return this value */ if (strncmp (text, "sec.data.", 9) == 0) { ptr_value = hashtable_get (secure_hashtable_data, text + 9); @@ -658,7 +669,7 @@ eval_replace_vars_cb (void *data, const char *text) } } - /* 15. local variable in buffer */ + /* 16. local variable in buffer */ ptr_buffer = hashtable_get (eval_context->pointers, "buffer"); if (ptr_buffer) { @@ -667,7 +678,7 @@ eval_replace_vars_cb (void *data, const char *text) return strdup (ptr_value); } - /* 16. hdata */ + /* 17. hdata */ value = NULL; hdata_name = NULL; list_name = NULL; |