aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/expressions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/expressions.c')
-rw-r--r--src/expressions.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/src/expressions.c b/src/expressions.c
new file mode 100644
index 0000000..9a00fee
--- /dev/null
+++ b/src/expressions.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2012-2021 Robin Haberkorn
+ *
+ * This program 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.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+
+#include "sciteco.h"
+#include "error.h"
+#include "undo.h"
+#include "expressions.h"
+
+/*
+ * Number and operator stacks are static, so
+ * they can be passed to the undo token constructors.
+ * This is OK since we're currently singleton.
+ */
+static GArray *teco_numbers;
+
+TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_int_t);
+TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_numbers);
+
+static GArray *teco_operators;
+
+TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_operators, teco_operator_t);
+TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_operators);
+
+static gboolean teco_expressions_calc(GError **error);
+
+static void __attribute__((constructor))
+teco_expressions_init(void)
+{
+ teco_numbers = g_array_sized_new(FALSE, FALSE, sizeof(teco_int_t), 1024);
+ teco_operators = g_array_sized_new(FALSE, FALSE, sizeof(teco_operator_t), 1024);
+}
+
+/** Get operator precedence */
+static inline gint
+teco_expressions_precedence(teco_operator_t op)
+{
+ return op >> 4;
+}
+
+gint teco_num_sign = 1;
+gint teco_radix = 10;
+
+void
+teco_expressions_push_int(teco_int_t number)
+{
+ while (teco_operators->len > 0 && teco_expressions_peek_op(0) == TECO_OP_NEW)
+ teco_expressions_pop_op(0);
+
+ teco_expressions_push_op(TECO_OP_NUMBER);
+
+ if (teco_num_sign < 0) {
+ teco_set_num_sign(1);
+ number *= -1;
+ }
+
+ g_array_append_val(teco_numbers, number);
+ undo__remove_index__teco_numbers(teco_numbers->len-1);
+}
+
+teco_int_t
+teco_expressions_peek_num(guint index)
+{
+ return g_array_index(teco_numbers, teco_int_t, teco_numbers->len - 1 - index);
+}
+
+teco_int_t
+teco_expressions_pop_num(guint index)
+{
+ teco_int_t n = 0;
+ teco_operator_t op = teco_expressions_pop_op(0);
+
+ g_assert(op == TECO_OP_NUMBER);
+
+ if (teco_numbers->len > 0) {
+ n = teco_expressions_peek_num(index);
+ undo__insert_val__teco_numbers(teco_numbers->len - 1 - index, n);
+ g_array_remove_index(teco_numbers, teco_numbers->len - 1 - index);
+ }
+
+ return n;
+}
+
+gboolean
+teco_expressions_pop_num_calc(teco_int_t *ret, teco_int_t imply, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return FALSE;
+ if (teco_num_sign < 0)
+ teco_set_num_sign(1);
+
+ teco_int_t v = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : imply;
+ if (ret)
+ *ret = v;
+ return TRUE;
+}
+
+void
+teco_expressions_add_digit(gchar digit)
+{
+ teco_int_t n = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : 0;
+
+ teco_expressions_push(n*teco_radix + (n < 0 ? -1 : 1)*(digit - '0'));
+}
+
+void
+teco_expressions_push_op(teco_operator_t op)
+{
+ g_array_append_val(teco_operators, op);
+ undo__remove_index__teco_operators(teco_operators->len-1);
+}
+
+gboolean
+teco_expressions_push_calc(teco_operator_t op, GError **error)
+{
+ gint first = teco_expressions_first_op();
+
+ /* calculate if op has lower precedence than op on stack */
+ if (first >= 0 &&
+ teco_expressions_precedence(op) <= teco_expressions_precedence(teco_expressions_peek_op(first)) &&
+ !teco_expressions_calc(error))
+ return FALSE;
+
+ teco_expressions_push_op(op);
+ return TRUE;
+}
+
+teco_operator_t
+teco_expressions_peek_op(guint index)
+{
+ return g_array_index(teco_operators, teco_operator_t, teco_operators->len - 1 - index);
+}
+
+teco_operator_t
+teco_expressions_pop_op(guint index)
+{
+ teco_operator_t op = TECO_OP_NIL;
+
+ if (teco_operators->len > 0) {
+ op = teco_expressions_peek_op(index);
+ undo__insert_val__teco_operators(teco_operators->len - 1 - index, op);
+ g_array_remove_index(teco_operators, teco_operators->len - 1 - index);
+ }
+
+ return op;
+}
+
+static gboolean
+teco_expressions_calc(GError **error)
+{
+ teco_int_t result;
+
+ if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Missing right operand");
+ return FALSE;
+ }
+ teco_int_t vright = teco_expressions_pop_num(0);
+ teco_operator_t op = teco_expressions_pop_op(0);
+ if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Missing left operand");
+ return FALSE;
+ }
+ teco_int_t vleft = teco_expressions_pop_num(0);
+
+ switch (op) {
+ case TECO_OP_POW:
+ for (result = 1; vright--; result *= vleft);
+ break;
+ case TECO_OP_MUL:
+ result = vleft * vright;
+ break;
+ case TECO_OP_DIV:
+ if (!vright) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Division by zero");
+ return FALSE;
+ }
+ result = vleft / vright;
+ break;
+ case TECO_OP_MOD:
+ if (!vright) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Remainder of division by zero");
+ return FALSE;
+ }
+ result = vleft % vright;
+ break;
+ case TECO_OP_ADD:
+ result = vleft + vright;
+ break;
+ case TECO_OP_SUB:
+ result = vleft - vright;
+ break;
+ case TECO_OP_AND:
+ result = vleft & vright;
+ break;
+ case TECO_OP_XOR:
+ result = vleft ^ vright;
+ break;
+ case TECO_OP_OR:
+ result = vleft | vright;
+ break;
+ default:
+ /* shouldn't happen */
+ g_assert_not_reached();
+ }
+
+ teco_expressions_push(result);
+ return TRUE;
+}
+
+gboolean
+teco_expressions_eval(gboolean pop_brace, GError **error)
+{
+ for (;;) {
+ gint n = teco_expressions_first_op();
+ if (n < 0)
+ break;
+
+ teco_operator_t op = teco_expressions_peek_op(n);
+ if (op == TECO_OP_BRACE) {
+ if (pop_brace)
+ teco_expressions_pop_op(n);
+ break;
+ }
+ if (n < 1)
+ break;
+
+ if (!teco_expressions_calc(error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+guint
+teco_expressions_args(void)
+{
+ guint n = 0;
+
+ while (n < teco_operators->len && teco_expressions_peek_op(n) == TECO_OP_NUMBER)
+ n++;
+
+ return n;
+}
+
+gint
+teco_expressions_first_op(void)
+{
+ for (guint i = 0; i < teco_operators->len; i++) {
+ switch (teco_expressions_peek_op(i)) {
+ case TECO_OP_NUMBER:
+ case TECO_OP_NEW:
+ break;
+ default:
+ return i;
+ }
+ }
+
+ return -1; /* no operator */
+}
+
+gboolean
+teco_expressions_discard_args(GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return FALSE;
+ for (guint i = teco_expressions_args(); i; i--)
+ if (!teco_expressions_pop_num_calc(NULL, 0, error))
+ return FALSE;
+ return TRUE;
+}
+
+/** The nesting level of braces */
+guint teco_brace_level = 0;
+
+void
+teco_expressions_brace_open(void)
+{
+ teco_expressions_push_op(TECO_OP_BRACE);
+ teco_undo_guint(teco_brace_level)++;
+}
+
+gboolean
+teco_expressions_brace_return(guint keep_braces, guint args, GError **error)
+{
+ /*
+ * FIXME: Allocating on the stack might be dangerous.
+ */
+ teco_int_t return_numbers[args];
+
+ for (guint i = args; i; i--)
+ return_numbers[i-1] = teco_expressions_pop_num(0);
+
+ teco_undo_guint(teco_brace_level);
+
+ while (teco_brace_level > keep_braces) {
+ if (!teco_expressions_discard_args(error) ||
+ !teco_expressions_eval(TRUE, error))
+ return FALSE;
+ teco_brace_level--;
+ }
+
+ for (guint i = 0; i < args; i++)
+ teco_expressions_push(return_numbers[i]);
+
+ return TRUE;
+}
+
+gboolean
+teco_expressions_brace_close(GError **error)
+{
+ if (!teco_brace_level) {
+ g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Missing opening brace");
+ return FALSE;
+ }
+ teco_undo_guint(teco_brace_level)--;
+ return teco_expressions_eval(TRUE, error);
+}
+
+void
+teco_expressions_clear(void)
+{
+ g_array_set_size(teco_numbers, 0);
+ g_array_set_size(teco_operators, 0);
+ teco_brace_level = 0;
+}
+
+/**
+ * Format a TECO integer as the `\` command would.
+ *
+ * @param buffer The output buffer of at least TECO_EXPRESSIONS_FORMAT_LEN characters.
+ * The output string will be null-terminated.
+ * @param number The number to format.
+ * @return A pointer into buffer to the beginning of the formatted number.
+ */
+gchar *
+teco_expressions_format(gchar *buffer, teco_int_t number)
+{
+ gchar *p = buffer + TECO_EXPRESSIONS_FORMAT_LEN;
+
+ teco_int_t v = ABS(number);
+
+ *--p = '\0';
+ do {
+ *--p = '0' + (v % teco_radix);
+ if (*p > '9')
+ *p += 'A' - '9' - 1;
+ } while ((v /= teco_radix));
+ if (number < 0)
+ *--p = '-';
+
+ return p;
+}
+
+#ifndef NDEBUG
+static void __attribute__((destructor))
+teco_expressions_cleanup(void)
+{
+ g_array_free(teco_numbers, TRUE);
+ g_array_free(teco_operators, TRUE);
+}
+#endif