From e0bf13622b0922252439e3fa8b43df9877678d75 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sun, 25 Oct 2026 02:39:11 +0100 Subject: numbers are separate parser states and there are syntactic octal and hexadecimal constants now * Instead of every digit being a stack-modifying standalone command, there is a single continuous parser state (teco_state_number) now - even though it has certain unique characteristics. They are consequently documented in the ARITHMETICS AND EXPRESSIONS chapter of sciteco(7). * A number will therefore be rubbed out entirely by CTRL+W, including a possible @-modifier. * (!) Spaces within constants are no longer ignored, but will push separate numbers to the stack instead (`1 2 3` pushes 3 numbers). The significance of the `,` operator is thus further reduced. * (!) You can no longer "append" to numbers on the stack as in `Qa001` - this was never actually used and caused more confusion. * (!) Digits outside the current radix are no longer accepted. E.g. digit 2 after `2^R`. This feature was also never used. * (!) Octal constants are supported with leading zeros as in C. This works regardless of the current radix set via `8^R` or `^O`. It remains to see whether this is actually useful or is rather annoying (since every constant beginning with 0 is automatically octal, you cannot left-pad arbitrary numbers with 0). * Hexadecimal constants are supported by at-modifying them as in `@0ff`. They must still begin with a digit 0-9 and since A-F are valid SciTECO commands, you may need additional spaces to separate hexadecimal constants from regular commands as in `@0ff D`. During syntax highlighting the number style is extended back to the at-sign. Note: Hexadecimal constants couldn't have been realized via `16^R` (or some shortcut) since that depends on runtime state, but we must know during parsing whether symbols A-F are part of the number of regular SciTECO commands. * The new hexadecimal constants are used in the color schemes now. Thanks to them, we no longer need workarounds like the "color.rgb" macro. * Since numbers don't have a dedicated terminator character, but any letter may terminate it, the number state must behave similar to a start state. Consequently, hexadecimal constants will currently be auto-case-folded if you enabled this feature. I recommend not to write hex constants in upper-case, though. Perhaps this should be worked around with a custom process_edit_cmd() callback. * Test suite updated --- src/core-commands.c | 106 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 21 deletions(-) (limited to 'src/core-commands.c') diff --git a/src/core-commands.c b/src/core-commands.c index 6362d95..f80cd59 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -55,6 +55,89 @@ 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 */ + + if (ctx->flags.mode > TECO_MODE_NORMAL) + return TRUE; + + 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) +{ + /* + * 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->integer here since + * we can assume that "^R" is a plain register. + */ + teco_qreg_t *qreg = ctx->qreg_table_locals->radix; + qreg->vtable->get_integer(qreg, &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; + + 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. @@ -760,32 +843,13 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) } switch (chr) { - /*$ 0 1 2 3 4 5 6 7 8 9 digit number - * [n]0|1|2|3|4|5|6|7|8|9 -> n*Radix+X -- Append digit - * - * Integer constants in \*(ST may be thought of and are - * technically sequences of single-digit commands. - * These commands take one argument from the stack - * (0 is implied), multiply it with the current radix - * (2, 8, 10, 16, ...), add the digit's value and - * return the resultant integer. - * - * The command-like semantics of digits may be abused - * in macros, for instance to append digits to computed - * integers. - * It is not an error to append a digit greater than the - * current radix - this may be changed in the future. - */ case '0' ... '9': - if (ctx->flags.modifier_at || - (ctx->flags.mode == TECO_MODE_NORMAL && ctx->flags.modifier_colon)) { + if (ctx->flags.mode == TECO_MODE_NORMAL && ctx->flags.modifier_colon) { gchar cmd[] = {chr, '\0'}; teco_error_modifier_set(error, cmd); return NULL; } - if (ctx->flags.mode == TECO_MODE_NORMAL) - teco_expressions_add_digit(chr, ctx->qreg_table_locals->radix); - return &teco_state_start; + return teco_state_number_input(ctx, chr, error); case '*': /* -- cgit v1.2.3