aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/expressions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/expressions.c')
-rw-r--r--src/expressions.c214
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)
{