aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/error.h9
-rw-r--r--src/number-commands.c166
-rw-r--r--src/parser.c1
-rw-r--r--src/parser.h10
4 files changed, 124 insertions, 62 deletions
diff --git a/src/error.h b/src/error.h
index 1306feb..19b707e 100644
--- a/src/error.h
+++ b/src/error.h
@@ -42,6 +42,7 @@ typedef enum {
TECO_ERROR_SYNTAX,
TECO_ERROR_MODIFIER,
TECO_ERROR_ARGEXPECTED,
+ TECO_ERROR_RADIX,
TECO_ERROR_LABEL,
TECO_ERROR_CODEPOINT,
TECO_ERROR_MOVE,
@@ -95,6 +96,14 @@ teco_error_argexpected_set(GError **error, const gchar *cmd)
}
static inline void
+teco_error_radix_set(GError **error, gchar chr, teco_int_t radix)
+{
+ g_set_error(error, TECO_ERROR, TECO_ERROR_RADIX,
+ "Digit \"%c\" not allowed in radix %" TECO_INT_FORMAT,
+ chr, radix);
+}
+
+static inline void
teco_error_label_set(GError **error, const gchar *name, gsize len)
{
g_autofree gchar *label_printable = teco_string_echo(name, len);
diff --git a/src/number-commands.c b/src/number-commands.c
index 27cf8ba..6ba2874 100644
--- a/src/number-commands.c
+++ b/src/number-commands.c
@@ -18,6 +18,8 @@
#include "config.h"
#endif
+#include <string.h>
+
#include <glib.h>
#include "sciteco.h"
@@ -28,6 +30,7 @@
#include "parser.h"
#include "interface.h"
#include "expressions.h"
+#include "string-utils.h"
#include "core-commands.h"
#include "number-commands.h"
@@ -36,85 +39,136 @@ static teco_state_t teco_state_number;
static gboolean
teco_state_number_end_of_macro(teco_machine_main_t *ctx, GError **error)
{
- teco_undo_guint(ctx->number.radix) = 0; /* default */
- guint divisor = ctx->number.divisor;
- teco_undo_guint(ctx->number.divisor) = 0; /* default */
+ teco_int_t radix = ctx->number.radix;
+ if (ctx->parent.must_undo)
+ teco_undo_guint(ctx->number.radix);
+ ctx->number.radix = 0; /* default */
if (ctx->flags.mode > TECO_MODE_NORMAL)
+ /* ctx->number.value hasn't been updated */
return TRUE;
- if (divisor)
- /* floating point number */
- teco_expressions_push((teco_float_t)ctx->number.value / divisor);
+ if (!radix)
+ /*
+ * The ^R radix is only considered if we do not
+ * syntactically signify the radix (octal or hex constants).
+ *
+ * FIXME: We could just access qreg->number.i here since
+ * we can assume that "^R" is a plain register.
+ */
+ teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL);
+
+ gboolean is_float = teco_string_contains(ctx->number.value, '.');
+ if (is_float) {
+ if (radix == 8 && g_str_has_prefix(ctx->number.value.data, "0."))
+ /* otherwise 0.1 would be parsed in octal */
+ radix = 10;
+
+ teco_float_t divisor = 0, value = 0;
+
+ for (const gchar *p = ctx->number.value.data; *p != '\0'; p++) {
+ if (*p == '.') {
+ divisor = 1;
+ continue;
+ }
+
+ gchar chr = teco_ascii_toupper(*p);
+ gint digit = 'A' <= chr && chr <= 'F' ? chr-'A'+10 : chr-'0';
+ if (digit >= radix) {
+ teco_error_radix_set(error, chr, radix);
+ return FALSE;
+ }
+
+ if (divisor)
+ divisor *= radix;
+ value = value*radix + teco_num_sign*digit;
+ }
+
+ teco_set_num_sign(1);
+ teco_expressions_push(value / divisor);
+ } else {
+ teco_int_t value = 0;
+
+ for (const gchar *p = ctx->number.value.data; *p != '\0'; p++) {
+ gchar chr = teco_ascii_toupper(*p);
+ gint digit = 'A' <= chr && chr <= 'F' ? chr-'A'+10 : chr-'0';
+ if (digit >= radix) {
+ teco_error_radix_set(error, chr, radix);
+ return FALSE;
+ }
+
+ /*
+ * By taking the teco_num_sign into account we can
+ * properly check for underflows.
+ * Otherwise MININT (-9223372036854775808) would overflow.
+ *
+ * NOTE: Glib 2.48 has g_size_checked_add().
+ */
+ if (__builtin_mul_overflow(value, radix, &value) ||
+ __builtin_add_overflow(value, teco_num_sign*digit, &value)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Integer constant overflow");
+ return FALSE;
+ }
+ }
+
+ teco_set_num_sign(1);
+ teco_expressions_push(value);
+ }
+
+ if (ctx->parent.must_undo)
+ teco_undo_string_own(ctx->number.value);
else
- teco_expressions_push(ctx->number.value);
- teco_undo_int(ctx->number.value) = 0;
+ teco_string_clear(&ctx->number.value);
+ memset(&ctx->number.value, 0, sizeof(ctx->number.value));
+
return TRUE;
}
teco_state_t *
teco_state_number_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
{
- /*
- * If you have several dots, the second one will push the current buffer position.
- * E.g. 23.0. is equivalent to 23.0,.
- */
- if (chr == '.' && !ctx->number.divisor) {
- /* floating point number */
- if (ctx->number.radix == 8 && ctx->number.value == 0)
- /* otherwise 0.1 would be parsed in octal */
- teco_undo_guint(ctx->number.radix) = 10;
- teco_undo_guint(ctx->number.divisor) = 1;
- return &teco_state_number;
- }
-
- /*
- * Hexadecimal constants (@[0-9][0-9a-zA-Z]*) are terminated by any non-hexadecimal character.
- * This means you may have to append a space or braces to stop parsing if the next
- * command would begin with such a letter (e.g. `@0ff D`).
- */
- gboolean end_of_number = chr <= 0xFF && (ctx->number.radix == 16 ? !g_ascii_isxdigit(chr)
- : !g_ascii_isdigit(chr));
+ gboolean end_of_number;
+ if (chr == '.')
+ /*
+ * If you have several dots, the second one will push the current buffer position.
+ * E.g. 23.0. is equivalent to 23.0,.
+ */
+ end_of_number = teco_string_contains(ctx->number.value, '.');
+ else
+ /*
+ * Hexadecimal constants (@[0-9][0-9a-zA-Z]*) are terminated by any non-hexadecimal character.
+ * This means you may have to append a space or braces to stop parsing if the next
+ * command would begin with such a letter (e.g. `@0ff D`).
+ */
+ end_of_number = chr > 0xFF || (ctx->number.radix == 16 ? !g_ascii_isxdigit(chr)
+ : !g_ascii_isdigit(chr));
if (end_of_number) {
- teco_state_number_end_of_macro(ctx, error);
+ if (!teco_state_number_end_of_macro(ctx, error))
+ return NULL;
return teco_state_start_input(ctx, chr, error);
}
if (!ctx->number.radix) {
- if (teco_machine_main_eval_at(ctx))
+ if (teco_machine_main_eval_at(ctx)) {
/* @[0-9] starts a hexadecimal constant */
- teco_undo_guint(ctx->number.radix) = 16;
- else if (!ctx->number.value && chr == '0')
+ if (ctx->parent.must_undo)
+ teco_undo_guint(ctx->number.radix);
+ ctx->number.radix = 16;
+ } else if (!ctx->number.value.len && chr == '0') {
/* a leading zero starts an octal constant */
- teco_undo_guint(ctx->number.radix) = 8;
+ if (ctx->parent.must_undo)
+ teco_undo_guint(ctx->number.radix);
+ ctx->number.radix = 8;
+ }
}
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_number;
- teco_int_t radix = ctx->number.radix;
- if (!radix)
- /*
- * The ^R radix is only considered if we do not
- * syntactically signify the radix (octal or hex constants).
- *
- * FIXME: We could just access qreg->number.i here since
- * we can assume that "^R" is a plain register.
- */
- teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL);
-
- chr = teco_ascii_toupper(chr);
- guint digit = 'A' <= chr && chr <= 'F' ? chr-'A'+10 : chr-'0';
- if (digit >= radix) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Digit \"%c\" not allowed in radix %" TECO_INT_FORMAT,
- (gchar)chr, radix);
- return NULL;
- }
-
- teco_undo_int(ctx->number.value) = ctx->number.value*radix + digit;
- if (ctx->number.divisor)
- teco_undo_guint(ctx->number.divisor) *= radix;
+ if (ctx->parent.must_undo)
+ undo__teco_string_truncate(&ctx->number.value, ctx->number.value.len);
+ teco_string_append_wc(&ctx->number.value, chr);
return &teco_state_number;
}
diff --git a/src/parser.c b/src/parser.c
index 231ad3e..28f6fd8 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -402,6 +402,7 @@ void
teco_machine_main_clear(teco_machine_main_t *ctx)
{
teco_goto_table_clear(&ctx->goto_table);
+ teco_string_clear(&ctx->number.value);
teco_string_clear(&ctx->expectstring.string);
teco_machine_stringbuilding_clear(&ctx->expectstring.machine);
teco_string_clear(&ctx->goto_label);
diff --git a/src/parser.h b/src/parser.h
index c134d94..8303a2d 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -441,14 +441,12 @@ typedef struct {
* In case of 0, "^R" determines the radix (10 by default).
*/
guint radix;
- /** accumulator for parsing numbers */
- teco_int_t value;
/**
- * if not null, value/divisor is the floating point number
- * FIXME: Perhaps roll this into a bitfield with radix.
- * The entire teco_machine_number_t could be undone.
+ * Accumulator for integer constants.
+ * It is not parsed immediately, so we can handle integer
+ * and float constants differently.
*/
- guint divisor;
+ teco_string_t value;
} teco_machine_number_t;
/**