diff options
Diffstat (limited to 'src/expressions.c')
| -rw-r--r-- | src/expressions.c | 214 |
1 files changed, 159 insertions, 55 deletions
diff --git a/src/expressions.c b/src/expressions.c index 95b1040..e4dc5ad 100644 --- a/src/expressions.c +++ b/src/expressions.c @@ -18,7 +18,12 @@ #include "config.h" #endif +#include <string.h> +/* type-generic math macros */ +#include <tgmath.h> + #include <glib.h> +#include <glib/gprintf.h> #include "sciteco.h" #include "error.h" @@ -33,7 +38,7 @@ */ static GArray *teco_numbers; -TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_int_t); +TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_number_t); TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_numbers); static GArray *teco_operators; @@ -46,7 +51,7 @@ 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_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); } @@ -60,16 +65,19 @@ teco_expressions_precedence(teco_operator_t op) gint teco_num_sign = 1; void -teco_expressions_push_int(teco_int_t number) +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(TECO_OP_NUMBER); + teco_expressions_push_op(is_float ? TECO_OP_FLOAT : TECO_OP_INTEGER); if (teco_num_sign < 0) { teco_set_num_sign(1); - number *= -1; + if (is_float) + number.f *= -1.0; + else + number.i *= -1; } g_array_append_val(teco_numbers, number); @@ -77,14 +85,14 @@ teco_expressions_push_int(teco_int_t number) } /** Peek into the numbers stack */ -teco_int_t +teco_number_t teco_expressions_peek_num(guint index) { - return g_array_index(teco_numbers, teco_int_t, teco_numbers->len - 1 - index); + return g_array_index(teco_numbers, teco_number_t, teco_numbers->len - 1 - index); } /** - * Pop a value from the number stack. + * 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 @@ -92,13 +100,13 @@ teco_expressions_peek_num(guint index) * If you are unsure or want to imply a value, * use teco_expressions_pop_num_calc() instead. */ -teco_int_t -teco_expressions_pop_num(guint index) +teco_number_t +teco_expressions_pop_num(guint index, gboolean *is_float) { - teco_int_t n = 0; - G_GNUC_UNUSED teco_operator_t op = teco_expressions_pop_op(0); + teco_number_t n = {0}; + teco_operator_t op = teco_expressions_pop_op(0); - g_assert(op == TECO_OP_NUMBER); + g_assert(op == TECO_OP_INTEGER || op == TECO_OP_FLOAT); if (teco_numbers->len > 0) { n = teco_expressions_peek_num(index); @@ -106,6 +114,8 @@ teco_expressions_pop_num(guint index) g_array_remove_index(teco_numbers, teco_numbers->len - 1 - index); } + if (is_float) + *is_float = op == TECO_OP_FLOAT; return n; } @@ -115,20 +125,28 @@ teco_expressions_pop_num(guint index) * This resolves operations and allows implying values * if there isn't any argument on the stack. * - * @param ret Where to store the number - * @param imply The fallback value if there is no argument + * @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_int_t *ret, teco_int_t imply, GError **error) +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_int_t v = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : imply; + 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; @@ -183,87 +201,146 @@ teco_expressions_pop_op(guint index) static gboolean teco_expressions_calc(GError **error) { - teco_int_t result; - - if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) { + 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; } - teco_int_t vright = teco_expressions_pop_num(0); + 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_NUMBER) { + + 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; } - teco_int_t vleft = teco_expressions_pop_num(0); + gboolean vleft_is_float; + teco_number_t vleft = teco_expressions_pop_num(0, &vleft_is_float); switch (op) { - case TECO_OP_POW: - if (!vright) { - result = vleft < 0 ? -1 : 1; + 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 < 0) { - if (!vleft) { + + 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; } - result = ABS(vleft) == 1 ? vleft : 0; + teco_expressions_push(ABS(vleft.i) == 1 ? vleft.i : 0); break; } - result = 1; + teco_int_t result = 1; for (;;) { - if (vright & 1) - result *= vleft; - vright >>= 1; - if (!vright) + if (vright.i & 1) + result *= vleft.i; + vright.i >>= 1; + if (!vright.i) break; - vleft *= vleft; + vleft.i *= vleft.i; } + teco_expressions_push(result); break; + } + case TECO_OP_MUL: - result = vleft * vright; + 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) { + if (!(vright_is_float ? vright.f : vright.i)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Division by zero"); return FALSE; } - result = vleft / vright; + + 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) { + 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; } - result = vleft % vright; + + 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: - result = vleft + vright; + 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: - result = vleft - vright; + 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: - result = vleft & vright; + 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: - result = vleft ^ vright; + /* + * 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: - result = vleft | vright; + 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(); } - teco_expressions_push(result); return TRUE; } @@ -311,7 +388,9 @@ teco_expressions_args(void) { guint n = 0; - while (n < teco_operators->len && teco_expressions_peek_op(n) == TECO_OP_NUMBER) + while (n < teco_operators->len && + (teco_expressions_peek_op(n) == TECO_OP_INTEGER || + teco_expressions_peek_op(n) == TECO_OP_FLOAT)) n++; return n; @@ -322,7 +401,8 @@ 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_INTEGER: + case TECO_OP_FLOAT: case TECO_OP_NEW: break; default: @@ -339,7 +419,7 @@ 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)) + if (!teco_expressions_pop_num_calc(NULL, NULL, 0, error)) return FALSE; return TRUE; } @@ -360,10 +440,15 @@ teco_expressions_brace_open(void) gboolean teco_expressions_brace_return(guint keep_braces, guint args, GError **error) { - g_autofree teco_int_t *return_numbers = g_new(teco_int_t, args); + 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] = teco_expressions_pop_num(0); + return_numbers[i-1].number = teco_expressions_pop_num(0, &return_numbers[i-1].is_float); teco_undo_guint(teco_brace_level); @@ -375,7 +460,7 @@ teco_expressions_brace_return(guint keep_braces, guint args, GError **error) } for (guint i = 0; i < args; i++) - teco_expressions_push(return_numbers[i]); + teco_expressions_push_num(return_numbers[i].number, return_numbers[i].is_float); return TRUE; } @@ -410,11 +495,11 @@ teco_expressions_clear(void) * @return A pointer into buffer to the beginning of the formatted number. */ gchar * -teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) +teco_expressions_format_int(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) { assert(qreg != NULL); teco_int_t radix = 10; - qreg->vtable->get_integer(qreg, &radix, NULL); + teco_qreg_get_integer(qreg, &radix, NULL); gchar *p = buffer + TECO_EXPRESSIONS_FORMAT_LEN; @@ -424,7 +509,7 @@ teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) do { *--p = '0' + ABS(v % radix); if (*p > '9') - *p += 'A' - '9' - 1; + *p += 'a' - '9' - 1; } while ((v /= radix)); if (number < 0) *--p = '-'; @@ -432,6 +517,25 @@ teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) 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) { |
