/*
* 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);
}