summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorSébastien Helleu <flashcode@flashtux.org>2019-09-20 21:37:01 +0200
committerSébastien Helleu <flashcode@flashtux.org>2019-09-20 21:37:01 +0200
commit997894edc04e87b0ba0c81712c92a83ec7f5c121 (patch)
tree8dc259f773da21c8592421da0323694843b146db /src/core
parent0109c519375f8a54552127e71ea2b00b1baa296e (diff)
downloadweechat-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.txt1
-rw-r--r--src/core/Makefile.am2
-rw-r--r--src/core/wee-calc.c355
-rw-r--r--src/core/wee-calc.h25
-rw-r--r--src/core/wee-command.c9
-rw-r--r--src/core/wee-eval.c23
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;