aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <rhaberkorn@fmsbw.de>2026-11-01 20:08:10 +0100
committerRobin Haberkorn <rhaberkorn@fmsbw.de>2026-11-01 20:44:46 +0100
commit5a4537cb67c77daba87d820ddcb4bd7574a8080d (patch)
tree61cb90b669827ad7f7521ce07d261a3bf597518a
parent1975c0d93373c41f9aef943980cb783188603e6d (diff)
refactored several commands and states related to numbers into number-commands.cHEADmaster-fmsbw-cimaster
This includes teco_state_number, `\`, all operators, braces, comma, `^_`, `^O`, `^D`, `^R`.
-rw-r--r--README2
-rw-r--r--src/Makefile.am1
-rw-r--r--src/core-commands.c369
-rw-r--r--src/number-commands.c403
-rw-r--r--src/number-commands.h44
5 files changed, 452 insertions, 367 deletions
diff --git a/README b/README
index a4a713d..fb2edd1 100644
--- a/README
+++ b/README
@@ -46,6 +46,8 @@ Features
more similar to other imperative languages.
* Operator precedence in arithmetic expressions and an argument stack that may be modified
by the user (commands may have more than two arguments)
+* Floating point arithmetics: a SciTECO number is either a signed integer or
+ floating point number (dynamic typing)
* Extended Q-Register namespace (arbitrary strings): Can be used to build libraries and
can be abused as a data structure.
* Quick access to the system clipboard via special `~` registers, even on ncurses!
diff --git a/src/Makefile.am b/src/Makefile.am
index eb532e3..d307227 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,6 +51,7 @@ libsciteco_base_la_SOURCES = main.c sciteco.h list.h \
ring.c ring.h \
parser.c parser.h \
core-commands.c core-commands.h \
+ number-commands.c number-commands.h \
move-commands.c move-commands.h \
stdio-commands.c stdio-commands.h \
search.c search.h \
diff --git a/src/core-commands.c b/src/core-commands.c
index 583142b..b7e7a60 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -45,6 +45,7 @@
#include "memory.h"
#include "eol.h"
#include "qreg.h"
+#include "number-commands.h"
#include "stdio-commands.h"
#include "qreg-commands.h"
#include "goto-commands.h"
@@ -55,108 +56,6 @@ static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar
static teco_state_t *teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
static teco_state_t *teco_state_endcond(teco_machine_main_t *ctx, const gchar *cmd, GError **error);
-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 */
-
- if (ctx->flags.mode > TECO_MODE_NORMAL)
- return TRUE;
-
- if (divisor)
- /* floating point number */
- teco_expressions_push((teco_float_t)ctx->number.value / divisor);
- else
- teco_expressions_push(ctx->number.value);
- teco_undo_int(ctx->number.value) = 0;
- return TRUE;
-}
-
-static 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));
- if (end_of_number) {
- teco_state_number_end_of_macro(ctx, error);
- return teco_state_start_input(ctx, chr, error);
- }
-
- if (!ctx->number.radix) {
- 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')
- /* a leading zero starts an octal constant */
- teco_undo_guint(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;
-
- return &teco_state_number;
-}
-
-/*
- * FIXME: This has to be a "command" since we chain to teco_state_start_input()
- * and want case-folding to work properly.
- * On the downside, this will also case-fold the hexadecimal constants (A-F).
- * There would be cumbersome workarounds with custom process_edit_cmd callbacks,
- * though.
- */
-static TECO_DEFINE_STATE_START(teco_state_number,
- .input_cb = (teco_state_input_cb_t)teco_state_number_input,
- .end_of_macro_cb = (teco_state_end_of_macro_cb_t)teco_state_number_end_of_macro,
- .style = SCE_SCITECO_NUMBER,
- .is_start = FALSE
-);
-
/**
* Translate buffer range arguments from the expression stack to
* a from-position and length in bytes.
@@ -232,76 +131,6 @@ teco_get_range_args(const gchar *cmd, teco_int_t *from_glyphs_ret, gsize *from_r
return TRUE;
}
-/*
- * NOTE: This needs some extra code in teco_state_start_input().
- */
-static void
-teco_state_start_mul(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_MUL, error);
-}
-
-static void
-teco_state_start_div(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_DIV, error);
-}
-
-static void
-teco_state_start_plus(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_ADD, error);
-}
-
-static void
-teco_state_start_minus(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_args())
- teco_set_num_sign(-teco_num_sign);
- else
- teco_expressions_push_calc(TECO_OP_SUB, error);
-}
-
-static void
-teco_state_start_and(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_AND, error);
-}
-
-static void
-teco_state_start_or(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_OR, error);
-}
-
-static void
-teco_state_start_brace_open(teco_machine_main_t *ctx, GError **error)
-{
- if (teco_num_sign < 0) {
- teco_set_num_sign(1);
- if (!teco_expressions_eval(FALSE, error))
- return;
- teco_expressions_push(-1);
- if (!teco_expressions_push_calc(TECO_OP_MUL, error))
- return;
- }
- teco_expressions_brace_open();
-}
-
-static void
-teco_state_start_brace_close(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_brace_close(error);
-}
-
-static void
-teco_state_start_comma(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_eval(FALSE, error))
- return;
- teco_expressions_push_op(TECO_OP_NEW);
-}
-
/*$ "." dot
* \&. -> dot -- Return buffer position
*
@@ -354,106 +183,6 @@ teco_state_start_range(teco_machine_main_t *ctx, GError **error)
teco_expressions_push(teco_interface_bytes2glyphs_absdot(pos));
}
-/*$ \[rs]
- * n\\ -- Insert or read ASCII numbers
- * \\ -> n
- *
- * Backslash pops a value from the stack, formats it
- * according to the current radix and inserts it in the
- * current buffer or Q-Register at dot.
- * Floating point numbers are formatted without an exponent
- * and 6 significant digits (precision), but whout trailing
- * zeros.
- *
- * If <n> is omitted (empty stack), it does the reverse \(em
- * it reads from the current buffer position a number
- * in the current radix and pushes it onto the stack
- * (either as an integer or float).
- * Dot is not changed when reading numbers.
- *
- * In other words, the command serializes or deserializes
- * numbers as ASCII characters.
- */
-static void
-teco_state_start_backslash(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_eval(FALSE, error))
- return;
-
- if (teco_expressions_args()) {
- gboolean is_float;
- teco_number_t number = teco_expressions_pop_num(0, &is_float);
-
- gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN];
- const gchar *str;
- str = is_float ? teco_expressions_format_float(buffer, number.f, 6)
- : teco_expressions_format_int(buffer, number.i,
- ctx->qreg_table_locals->radix);
- g_assert(*str != '\0');
- gsize len = strlen(str);
-
- teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot();
- /*
- * We can assume that `len` is already in glyphs,
- * i.e. formatted numbers will never use multi-byte/Unicode characters.
- */
- teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len;
- teco_undo_guint(teco_ranges_count) = 1;
-
- teco_current_doc_set_dot(teco_ranges[0].to);
-
- if (teco_current_doc_must_undo())
- undo__teco_interface_ssm(SCI_DELETERANGE,
- teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), len);
- teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)str);
- teco_ring_dirtify();
- } else {
- teco_int_t radix = 10;
- teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL);
-
- /*
- * FIXME: Should we also support octal constants automatically when seeing
- * a leading 0 or even hexadecimal via @[0-9]?
- */
- uptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
- gchar c = (gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0);
- teco_int_t value = 0;
- guint divisor = 0;
- gint sign = 1;
-
- if (c == '-') {
- pos++;
- sign = -1;
- }
-
- for (;; pos++) {
- c = teco_ascii_toupper((gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0));
-
- if (c == '.') {
- /* floating point number */
- divisor = 1;
- continue;
- }
-
- if (c >= '0' && c <= '0' + MIN(radix, 10) - 1)
- value = (value*radix) + (c - '0');
- else if (c >= 'A' &&
- c <= 'A' + MIN(radix - 10, 26) - 1)
- value = (value*radix) + 10 + (c - 'A');
- else
- break;
-
- if (divisor)
- divisor *= radix;
- }
-
- if (divisor)
- teco_expressions_push(sign * ((teco_float_t)value / divisor));
- else
- teco_expressions_push(sign * value);
- }
-}
-
/*
* NOTE: This needs some extra code in teco_state_start_input().
*/
@@ -1440,101 +1169,6 @@ teco_state_endcond(teco_machine_main_t *ctx, const gchar *cmd, GError **error)
return &teco_state_start;
}
-/*$ ^_ negate
- * n^_ -> ~n -- Binary negation
- *
- * Binary negates (complements) <n> and returns
- * the result.
- * Binary complements are often used to negate
- * \*(ST booleans.
- */
-static void
-teco_state_control_negate(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_eval(FALSE, error))
- return;
-
- if (!teco_expressions_args()) {
- teco_error_argexpected_set(error, "^_");
- return;
- }
-
- teco_expressions_push(~teco_expressions_pop_int(0));
-}
-
-static void
-teco_state_control_pow(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_POW, error);
-}
-
-static void
-teco_state_control_mod(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_MOD, error);
-}
-
-static void
-teco_state_control_xor(teco_machine_main_t *ctx, GError **error)
-{
- teco_expressions_push_calc(TECO_OP_XOR, error);
-}
-
-/*$ ^O octal
- * ^O -- Set radix to 8 (octal)
- */
-static void
-teco_state_control_octal(teco_machine_main_t *ctx, GError **error)
-{
- teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
- assert(qreg != NULL);
- if (!teco_qreg_set_integer(qreg, 8, NULL))
- return;
-}
-
-/*$ ^D decimal
- * ^D -- Set radix to 10 (decimal)
- */
-static void
-teco_state_control_decimal(teco_machine_main_t *ctx, GError **error)
-{
- teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
- assert(qreg != NULL);
- if (!teco_qreg_set_integer(qreg, 10, NULL))
- return;
-}
-
-/*$ ^R radix
- * radix^R -- Set and get radix
- * ^R -> radix
- *
- * Set current radix to any value <radix> larger than or equal to 2.
- * If <radix> is omitted, the command instead
- * returns the current radix.
- *
- * An alternative way to access the radix is via the \(lq^R\(rq local Q-Register.
- * Consequently, the radix is local to the current macro invocation frame,
- * unless the macro call was colon-modified.
- */
-static void
-teco_state_control_radix(teco_machine_main_t *ctx, GError **error)
-{
- if (!teco_expressions_eval(FALSE, error))
- return;
-
- teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
- assert(qreg != NULL);
- teco_int_t radix = 10;
-
- if (!teco_expressions_args()) {
- teco_qreg_get_integer(qreg, &radix, NULL);
- teco_expressions_push(radix);
- } else {
- if (!teco_qreg_set_integer(qreg, teco_expressions_pop_int(0), error))
- return;
- }
-}
-
/*$ "^E" ":^E" glyphs2bytes bytes2glyphs
* glyphs^E -> bytes -- Translate between glyph and byte indexes
* bytes:^E -> glyphs
@@ -1756,6 +1390,7 @@ teco_state_control_time(teco_machine_main_t *ctx, GError **error)
*
* First sleep <n> milliseconds before refreshing the view,
* i.e. drawing it.
+ * <n> can be a floating point number.
* By default it sleeps for 10ms.
* This can be added to loops to make progress visible
* in interactive mode.
diff --git a/src/number-commands.c b/src/number-commands.c
new file mode 100644
index 0000000..27cf8ba
--- /dev/null
+++ b/src/number-commands.c
@@ -0,0 +1,403 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+
+#include "sciteco.h"
+#include "undo.h"
+#include "qreg.h"
+#include "lexer.h"
+#include "ring.h"
+#include "parser.h"
+#include "interface.h"
+#include "expressions.h"
+#include "core-commands.h"
+#include "number-commands.h"
+
+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 */
+
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return TRUE;
+
+ if (divisor)
+ /* floating point number */
+ teco_expressions_push((teco_float_t)ctx->number.value / divisor);
+ else
+ teco_expressions_push(ctx->number.value);
+ teco_undo_int(ctx->number.value) = 0;
+ 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));
+ if (end_of_number) {
+ teco_state_number_end_of_macro(ctx, error);
+ return teco_state_start_input(ctx, chr, error);
+ }
+
+ if (!ctx->number.radix) {
+ 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')
+ /* a leading zero starts an octal constant */
+ teco_undo_guint(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;
+
+ return &teco_state_number;
+}
+
+/*
+ * FIXME: This has to be a "command" since we chain to teco_state_start_input()
+ * and want case-folding to work properly.
+ * On the downside, this will also case-fold the hexadecimal constants (A-F).
+ * There would be cumbersome workarounds with custom process_edit_cmd callbacks,
+ * though.
+ */
+static TECO_DEFINE_STATE_START(teco_state_number,
+ .input_cb = (teco_state_input_cb_t)teco_state_number_input,
+ .end_of_macro_cb = (teco_state_end_of_macro_cb_t)teco_state_number_end_of_macro,
+ .style = SCE_SCITECO_NUMBER,
+ .is_start = FALSE
+);
+
+/*
+ * Transition table callbacks
+ */
+
+/*$ \[rs]
+ * n\\ -- Insert or read ASCII numbers
+ * \\ -> n
+ *
+ * Backslash pops a value from the stack, formats it
+ * according to the current radix and inserts it in the
+ * current buffer or Q-Register at dot.
+ * Floating point numbers are formatted without an exponent
+ * and 6 significant digits (precision), but whout trailing
+ * zeros.
+ *
+ * If <n> is omitted (empty stack), it does the reverse \(em
+ * it reads from the current buffer position a number
+ * in the current radix and pushes it onto the stack
+ * (either as an integer or float).
+ * Dot is not changed when reading numbers.
+ *
+ * In other words, the command serializes or deserializes
+ * numbers as ASCII characters.
+ */
+void
+teco_state_start_backslash(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+
+ if (teco_expressions_args()) {
+ gboolean is_float;
+ teco_number_t number = teco_expressions_pop_num(0, &is_float);
+
+ gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN];
+ const gchar *str;
+ str = is_float ? teco_expressions_format_float(buffer, number.f, 6)
+ : teco_expressions_format_int(buffer, number.i,
+ ctx->qreg_table_locals->radix);
+ g_assert(*str != '\0');
+ gsize len = strlen(str);
+
+ teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot();
+ /*
+ * We can assume that `len` is already in glyphs,
+ * i.e. formatted numbers will never use multi-byte/Unicode characters.
+ */
+ teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len;
+ teco_undo_guint(teco_ranges_count) = 1;
+
+ teco_current_doc_set_dot(teco_ranges[0].to);
+
+ if (teco_current_doc_must_undo())
+ undo__teco_interface_ssm(SCI_DELETERANGE,
+ teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), len);
+ teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)str);
+ teco_ring_dirtify();
+ } else {
+ teco_int_t radix = 10;
+ teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL);
+
+ /*
+ * FIXME: Should we also support octal constants automatically when seeing
+ * a leading 0 or even hexadecimal via @[0-9]?
+ */
+ uptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ gchar c = (gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0);
+ teco_int_t value = 0;
+ guint divisor = 0;
+ gint sign = 1;
+
+ if (c == '-') {
+ pos++;
+ sign = -1;
+ }
+
+ for (;; pos++) {
+ c = teco_ascii_toupper((gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0));
+
+ if (c == '.') {
+ /* floating point number */
+ divisor = 1;
+ continue;
+ }
+
+ if (c >= '0' && c <= '0' + MIN(radix, 10) - 1)
+ value = (value*radix) + (c - '0');
+ else if (c >= 'A' &&
+ c <= 'A' + MIN(radix - 10, 26) - 1)
+ value = (value*radix) + 10 + (c - 'A');
+ else
+ break;
+
+ if (divisor)
+ divisor *= radix;
+ }
+
+ if (divisor)
+ teco_expressions_push(sign * ((teco_float_t)value / divisor));
+ else
+ teco_expressions_push(sign * value);
+ }
+}
+
+/*
+ * NOTE: This needs some extra code in teco_state_start_input().
+ */
+void
+teco_state_start_mul(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_MUL, error);
+}
+
+void
+teco_state_start_div(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_DIV, error);
+}
+
+void
+teco_state_start_plus(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_ADD, error);
+}
+
+void
+teco_state_start_minus(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_args())
+ teco_set_num_sign(-teco_num_sign);
+ else
+ teco_expressions_push_calc(TECO_OP_SUB, error);
+}
+
+void
+teco_state_start_and(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_AND, error);
+}
+
+void
+teco_state_start_or(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_OR, error);
+}
+
+void
+teco_state_start_brace_open(teco_machine_main_t *ctx, GError **error)
+{
+ if (teco_num_sign < 0) {
+ teco_set_num_sign(1);
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+ teco_expressions_push(-1);
+ if (!teco_expressions_push_calc(TECO_OP_MUL, error))
+ return;
+ }
+ teco_expressions_brace_open();
+}
+
+void
+teco_state_start_brace_close(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_brace_close(error);
+}
+
+void
+teco_state_start_comma(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+ teco_expressions_push_op(TECO_OP_NEW);
+}
+
+/*$ ^_ negate
+ * n^_ -> ~n -- Binary negation
+ *
+ * Binary negates (complements) <n> and returns
+ * the result.
+ * Binary complements are often used to negate
+ * \*(ST booleans.
+ */
+void
+teco_state_control_negate(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+
+ if (!teco_expressions_args()) {
+ teco_error_argexpected_set(error, "^_");
+ return;
+ }
+
+ teco_expressions_push(~teco_expressions_pop_int(0));
+}
+
+void
+teco_state_control_pow(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_POW, error);
+}
+
+void
+teco_state_control_mod(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_MOD, error);
+}
+
+void
+teco_state_control_xor(teco_machine_main_t *ctx, GError **error)
+{
+ teco_expressions_push_calc(TECO_OP_XOR, error);
+}
+
+/*$ ^O octal
+ * ^O -- Set radix to 8 (octal)
+ */
+void
+teco_state_control_octal(teco_machine_main_t *ctx, GError **error)
+{
+ teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
+ g_assert(qreg != NULL);
+ if (!teco_qreg_set_integer(qreg, 8, NULL))
+ return;
+}
+
+/*$ ^D decimal
+ * ^D -- Set radix to 10 (decimal)
+ */
+void
+teco_state_control_decimal(teco_machine_main_t *ctx, GError **error)
+{
+ teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
+ g_assert(qreg != NULL);
+ if (!teco_qreg_set_integer(qreg, 10, NULL))
+ return;
+}
+
+/*$ ^R radix
+ * radix^R -- Set and get radix
+ * ^R -> radix
+ *
+ * Set current radix to any value <radix> larger than or equal to 2.
+ * If <radix> is omitted, the command instead
+ * returns the current radix.
+ *
+ * An alternative way to access the radix is via the \(lq^R\(rq local Q-Register.
+ * Consequently, the radix is local to the current macro invocation frame,
+ * unless the macro call was colon-modified.
+ */
+void
+teco_state_control_radix(teco_machine_main_t *ctx, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+
+ teco_qreg_t *qreg = ctx->qreg_table_locals->radix;
+ g_assert(qreg != NULL);
+ teco_int_t radix = 10;
+
+ if (!teco_expressions_args()) {
+ teco_qreg_get_integer(qreg, &radix, NULL);
+ teco_expressions_push(radix);
+ } else {
+ if (!teco_qreg_set_integer(qreg, teco_expressions_pop_int(0), error))
+ return;
+ }
+}
diff --git a/src/number-commands.h b/src/number-commands.h
new file mode 100644
index 0000000..79fa77b
--- /dev/null
+++ b/src/number-commands.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <glib.h>
+
+#include "sciteco.h"
+#include "parser.h"
+
+teco_state_t *teco_state_number_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
+
+void teco_state_start_backslash(teco_machine_main_t *ctx, GError **error);
+
+void teco_state_start_mul(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_div(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_plus(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_minus(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_and(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_or(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_brace_open(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_brace_close(teco_machine_main_t *ctx, GError **error);
+void teco_state_start_comma(teco_machine_main_t *ctx, GError **error);
+
+void teco_state_control_negate(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_pow(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_mod(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_xor(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_octal(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_decimal(teco_machine_main_t *ctx, GError **error);
+void teco_state_control_radix(teco_machine_main_t *ctx, GError **error);