/* * Copyright (C) 2012-2026 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 . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include /* type-generic math macros */ #include #include #include #include "sciteco.h" #include "error.h" #include "undo.h" #include "qreg.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_number_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_number_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; void teco_expressions_push_num(teco_number_t number, gboolean is_float) { while (teco_operators->len > 0 && teco_expressions_peek_op(0) == TECO_OP_NEW) teco_expressions_pop_op(0); teco_expressions_push_op(is_float ? TECO_OP_FLOAT : TECO_OP_INTEGER); if (teco_num_sign < 0) { teco_set_num_sign(1); if (is_float) number.f *= -1.0; else number.i *= -1; } g_array_append_val(teco_numbers, number); undo__remove_index__teco_numbers(teco_numbers->len-1); } /** Peek into the numbers stack */ teco_number_t teco_expressions_peek_num(guint index) { return g_array_index(teco_numbers, teco_number_t, teco_numbers->len - 1 - index); } /** * Pop a number from the number stack. * * This must only be called if you are sure that the number at the * given index exists and is an argument, i.e. only after calling * teco_expressions_eval() and teco_expressions_args(). * If you are unsure or want to imply a value, * use teco_expressions_pop_num_calc() instead. */ teco_number_t teco_expressions_pop_num(guint index, gboolean *is_float) { teco_number_t n = {0}; teco_operator_t op = teco_expressions_pop_op(0); g_assert(op == TECO_OP_INTEGER || op == TECO_OP_FLOAT); 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); } if (is_float) *is_float = op == TECO_OP_FLOAT; return n; } /** * Pop an argument from the number stack. * * This resolves operations and allows implying values * if there isn't any argument on the stack. * * @param ret Where to store the number (can be NULL) * @param is_float Whether the number in ret is a teco_float_t (can be NULL). * @param imply The fallback integer if there is no argument * @param error A GError * @return FALSE if an error occurred */ gboolean teco_expressions_pop_num_calc(teco_number_t *ret, gboolean *is_float, 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_number_t v; if (teco_expressions_args() > 0) { v = teco_expressions_pop_num(0, is_float); } else { v.i = imply; if (is_float) *is_float = FALSE; } if (ret) *ret = v; return TRUE; } 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) { for (;;) { 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))) break; if (!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) { if (!teco_operators->len || (teco_expressions_peek_op(0) != TECO_OP_INTEGER && teco_expressions_peek_op(0) != TECO_OP_FLOAT)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Missing right operand"); return FALSE; } gboolean vright_is_float; teco_number_t vright = teco_expressions_pop_num(0, &vright_is_float); teco_operator_t op = teco_expressions_pop_op(0); if (!teco_operators->len || (teco_expressions_peek_op(0) != TECO_OP_INTEGER && teco_expressions_peek_op(0) != TECO_OP_FLOAT)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Missing left operand"); return FALSE; } gboolean vleft_is_float; teco_number_t vleft = teco_expressions_pop_num(0, &vleft_is_float); switch (op) { case TECO_OP_POW: { if (vleft_is_float || vright_is_float) { teco_expressions_push(pow(vleft_is_float ? vleft.f : vleft.i, vright_is_float ? vright.f : vright.i)); break; } if (!vright.i) { teco_expressions_push(vleft.i < 0 ? -1 : 1); break; } if (vright.i < 0) { if (!vleft.i) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Negative power of 0 is not defined"); return FALSE; } teco_expressions_push(ABS(vleft.i) == 1 ? vleft.i : 0); break; } teco_int_t result = 1; for (;;) { if (vright.i & 1) result *= vleft.i; vright.i >>= 1; if (!vright.i) break; vleft.i *= vleft.i; } teco_expressions_push(result); break; } case TECO_OP_MUL: if (vleft_is_float || vright_is_float) { teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) * (vright_is_float ? vright.f : vright.i)); break; } teco_expressions_push(vleft.i * vright.i); break; case TECO_OP_DIV: if (!(vright_is_float ? vright.f : vright.i)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Division by zero"); return FALSE; } if (vleft_is_float || vright_is_float) { teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) / (vright_is_float ? vright.f : vright.i)); break; } teco_expressions_push(vleft.i / vright.i); break; case TECO_OP_MOD: if (!(vright_is_float ? vright.f : vright.i)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Remainder of division by zero"); return FALSE; } if (vleft_is_float || vright_is_float) { teco_expressions_push(fmod(vleft_is_float ? vleft.f : vleft.i, vright_is_float ? vright.f : vright.i)); break; } teco_expressions_push(vleft.i % vright.i); break; case TECO_OP_ADD: if (vleft_is_float || vright_is_float) { teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) + (vright_is_float ? vright.f : vright.i)); break; } teco_expressions_push(vleft.i + vright.i); break; case TECO_OP_SUB: if (vleft_is_float || vright_is_float) { teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) - (vright_is_float ? vright.f : vright.i)); break; } teco_expressions_push(vleft.i - vright.i); break; case TECO_OP_AND: teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) & (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; case TECO_OP_XOR: /* * As floats are casted to integers first, discarding the mantissa, * you cannot use XOR to compare two floats for equality. */ teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) ^ (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; case TECO_OP_OR: teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) | (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; default: /* shouldn't happen */ g_assert_not_reached(); } return TRUE; } /** * Resolve all operations on the top of the stack. * * @param pop_brace If TRUE this also pops the "brace" operator. * @param error A GError * @return FALSE if an error occurred */ 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; } /** * Get number of numeric arguments on the top of the stack. * * @fixme You must call teco_expressions_eval() to resolve operations * before this gives sensitive results. * Overall it might be better to automatically call teco_expressions_eval() * here or introduce a separate teco_expressions_args_calc(). */ guint teco_expressions_args(void) { guint n = 0; while (n < teco_operators->len && (teco_expressions_peek_op(n) == TECO_OP_INTEGER || teco_expressions_peek_op(n) == TECO_OP_FLOAT)) 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_INTEGER: case TECO_OP_FLOAT: 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, NULL, 0, error)) return FALSE; return TRUE; } /** The nesting level of braces */ guint teco_brace_level = 0; void teco_expressions_brace_open(void) { while (teco_operators->len > 0 && teco_expressions_peek_op(0) == TECO_OP_NEW) teco_expressions_pop_op(0); 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) { typedef struct { teco_number_t number; gboolean is_float; } teco_number_tagged_t; g_autofree teco_number_tagged_t *return_numbers = g_new(teco_number_tagged_t, args); for (guint i = args; i; i--) return_numbers[i-1].number = teco_expressions_pop_num(0, &return_numbers[i-1].is_float); 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_num(return_numbers[i].number, return_numbers[i].is_float); 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. * @param qreg The radix register (^R). * @return A pointer into buffer to the beginning of the formatted number. */ gchar * teco_expressions_format_int(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) { assert(qreg != NULL); teco_int_t radix = 10; teco_qreg_get_integer(qreg, &radix, NULL); gchar *p = buffer + TECO_EXPRESSIONS_FORMAT_LEN; teco_int_t v = number; *--p = '\0'; do { *--p = '0' + ABS(v % radix); if (*p > '9') *p += 'a' - '9' - 1; } while ((v /= radix)); if (number < 0) *--p = '-'; return p; } gchar * teco_expressions_format_float(gchar *buffer, teco_float_t number, guint precision) { /* * Do not print in scientific notation (with an exponent) - we cannot * write such constants in SciTECO, so we don't want to format them either. */ g_sprintf(buffer, "%.*" TECO_FLOAT_MODIFIER "f", precision, number); gchar *p = buffer + strlen(buffer); /* * On the other hand, this prints a fixed number of trailing zeros, so we * strip these afterwards. */ while (p > buffer && (p[-1] == '0' || p[-1] == '.')) p--; *p = '\0'; return buffer; } static void TECO_DEBUG_CLEANUP teco_expressions_cleanup(void) { g_array_free(teco_numbers, TRUE); g_array_free(teco_operators, TRUE); }