From 9425ad37ec95a40dc039169031259161c92cc217 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sun, 31 Aug 2025 02:24:11 +0300 Subject: support <:O>: if a label is not found, continue execution after the go-to statement * this is a SciTECO extension - it's not in TECO-11 * Allows for select-case-like constructs with default-clauses as in :Os.^EQa$ !* default *! !s.foo! !* ... *! !s.bar! !* ... *! * Consistent with nOlabel0,label1,...$ if is out of range. Unfortunately this form of computed goto is not applicable when "selecting" by strings or non-consecutive integers. * In order to continue after the <:O> statement, we must keep the program counter along with the label we were looking for. At the end of the macro, the PC is restored instead of throwing an error. * Since that would be very inefficient in loops - where potentially all iterations would result in rescanning till the end of the macro - we now store a completed-flag in the goto table. If it is set while trying to :O to an unknown label, we can just continue execution. --- src/core-commands.c | 2 +- src/error.h | 9 +++++++ src/goto-commands.c | 34 ++++++++++++++++++++---- src/goto-commands.h | 3 +++ src/goto.h | 9 +++++-- src/parser.c | 75 +++++++++++++++++++++++++++++++++-------------------- src/undo.c | 1 + src/undo.h | 3 +++ 8 files changed, 100 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/core-commands.c b/src/core-commands.c index 5fe960b..8ce7a24 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -633,7 +633,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error) ['$'] = {&teco_state_escape}, ['!'] = {&teco_state_label}, ['O'] = {&teco_state_goto, - .modifier_at = TRUE}, + .modifier_at = TRUE, .modifier_colon = 1}, ['^'] = {&teco_state_control, .modifier_at = TRUE, .modifier_colon = 2}, ['F'] = {&teco_state_fcommand, diff --git a/src/error.h b/src/error.h index b4f92ad..2a12733 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_LABEL, TECO_ERROR_CODEPOINT, TECO_ERROR_MOVE, TECO_ERROR_WORDS, @@ -91,6 +92,14 @@ teco_error_argexpected_set(GError **error, const gchar *cmd) "Argument expected for <%s>", cmd); } +static inline void +teco_error_label_set(GError **error, const gchar *name, gsize len) +{ + g_autofree gchar *label_printable = teco_string_echo(name, len); + g_set_error(error, TECO_ERROR, TECO_ERROR_LABEL, + "Label \"%s\" not found", label_printable); +} + static inline void teco_error_codepoint_set(GError **error, const gchar *cmd) { diff --git a/src/goto-commands.c b/src/goto-commands.c index ac98b70..73575ca 100644 --- a/src/goto-commands.c +++ b/src/goto-commands.c @@ -38,7 +38,17 @@ TECO_DECLARE_STATE(teco_state_blockcomment); TECO_DECLARE_STATE(teco_state_eolcomment); +/** + * In TECO_MODE_PARSE_ONLY_GOTO mode, we remain in parse-only mode + * until the given label is encountered. + */ teco_string_t teco_goto_skip_label = {NULL, 0}; +/** + * The program counter to restore if the teco_goto_skip_label + * is \b not found (after :Olabel$). + * If smaller than 0 an error is thrown instead. + */ +gssize teco_goto_backup_pc = -1; /* * NOTE: The comma is theoretically not allowed in a label @@ -68,6 +78,7 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error) !teco_string_cmp(&ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) { teco_undo_string_own(teco_goto_skip_label); memset(&teco_goto_skip_label, 0, sizeof(teco_goto_skip_label)); + teco_undo_gssize(teco_goto_backup_pc) = -1; if (ctx->parent.must_undo) teco_undo_flags(ctx->flags); @@ -122,6 +133,8 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError if (!teco_expressions_pop_num_calc(&value, 0, error)) return NULL; + gboolean colon_modified = teco_machine_main_eval_colon(ctx) > 0; + /* * Find the comma-separated substring in str indexed by `value`. */ @@ -142,14 +155,19 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError if (pc >= 0) { ctx->macro_pc = pc; - } else { + } else if (!ctx->goto_table.complete) { /* skip till label is defined */ g_assert(teco_goto_skip_label.len == 0); undo__teco_string_truncate(&teco_goto_skip_label, 0); teco_string_init(&teco_goto_skip_label, label.data, label.len); + teco_undo_gssize(teco_goto_backup_pc) = colon_modified ? ctx->macro_pc : -1; if (ctx->parent.must_undo) teco_undo_flags(ctx->flags); ctx->flags.mode = TECO_MODE_PARSE_ONLY_GOTO; + } else if (!colon_modified) { + /* can happen if we previously executed a colon-modified go-to */ + teco_error_label_set(error, teco_goto_skip_label.data, teco_goto_skip_label.len); + return NULL; } } @@ -164,6 +182,7 @@ gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_ /*$ "O" goto * Olabel$ -- Go to label + * :Olabel$ * [n]Olabel0[,label1,...]$ * * Go to