aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO3
-rw-r--r--src/core-commands.c2
-rw-r--r--src/error.h9
-rw-r--r--src/goto-commands.c34
-rw-r--r--src/goto-commands.h3
-rw-r--r--src/goto.h9
-rw-r--r--src/parser.c75
-rw-r--r--src/undo.c1
-rw-r--r--src/undo.h3
-rw-r--r--tests/testsuite.at1
10 files changed, 101 insertions, 39 deletions
diff --git a/TODO b/TODO
index 128fa6a..ddb5b76 100644
--- a/TODO
+++ b/TODO
@@ -729,9 +729,6 @@ Features:
This would ease subscripting, e.g. to remove the last character:
0,:Qq-1Qq^Uq$
There should also be m,nA for consistency.
- * :Olabel$ to avoid throwing an error if label is not defined.
- This would be very useful when using computed Gotos as select-case
- like constructs as the code after the Goto would act as a default-clause.
* Sort order in Q-register autocompletions should be numeric,
so ^A11 comes after ^A1.
* FFI interface: There could be a command nFXlib$func$str$
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,
@@ -92,6 +93,14 @@ teco_error_argexpected_set(GError **error, const gchar *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)
{
g_set_error(error, TECO_ERROR, TECO_ERROR_CODEPOINT,
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 <label>.
@@ -186,14 +205,19 @@ gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_
* Otherwise, parsing continues until the <label>
* is defined.
* The command will yield an error if a label has
- * not been defined when the macro or command-line
- * is terminated.
- * In the latter case, the user will not be able to
- * terminate the command-line.
+ * not been defined when the macro is terminated.
+ * When jumping to a non-existent <label> in the
+ * command-line macro, you cannot practically terminate
+ * the command-line until defining the <label>.
*
* String building constructs are enabled in \fBO\fP
* which allows for a second kind of computed go-to,
* where the label name contains the value to select.
+ * When colon-modifying the \fBO\fP command, execution
+ * will continue after the command if the given <label>
+ * isn't found.
+ * This is useful to handle the \(lqdefault\(rq case
+ * when using computed go-tos of the second kind.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd,
diff --git a/src/goto-commands.h b/src/goto-commands.h
index f4f52d5..45c68e9 100644
--- a/src/goto-commands.h
+++ b/src/goto-commands.h
@@ -16,10 +16,13 @@
*/
#pragma once
+#include <glib.h>
+
#include "parser.h"
#include "string-utils.h"
extern teco_string_t teco_goto_skip_label;
+extern gssize teco_goto_backup_pc;
TECO_DECLARE_STATE(teco_state_label);
TECO_DECLARE_STATE(teco_state_goto);
diff --git a/src/goto.h b/src/goto.h
index eda823f..05c7598 100644
--- a/src/goto.h
+++ b/src/goto.h
@@ -26,10 +26,14 @@
typedef struct {
teco_rb3str_tree_t tree;
+ /** Whether to generate undo tokens (unnecessary in macro invocations) */
+ guint must_undo : 1;
+
/**
- * Whether to generate undo tokens (unnecessary in macro invocations)
+ * Whether the table is guaranteed to be complete because the entire
+ * macro has already been parsed.
*/
- gboolean must_undo;
+ guint complete : 1;
} teco_goto_table_t;
/** @memberof teco_goto_table_t */
@@ -38,6 +42,7 @@ teco_goto_table_init(teco_goto_table_t *ctx, gboolean must_undo)
{
rb3_reset_tree(&ctx->tree);
ctx->must_undo = must_undo;
+ ctx->complete = FALSE;
}
gboolean teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
diff --git a/src/parser.c b/src/parser.c
index 6d4cd60..a5e6e4f 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -186,41 +186,60 @@ teco_execute_macro(const gchar *macro, gsize macro_len,
GError *tmp_error = NULL;
- if (!teco_machine_main_step(&macro_machine, macro, macro_len, &tmp_error)) {
- if (!g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) {
- /* passes ownership of tmp_error */
- g_propagate_error(error, tmp_error);
- goto error_cleanup;
+ for (;;) {
+ if (!teco_machine_main_step(&macro_machine, macro, macro_len, &tmp_error)) {
+ if (!g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_RETURN)) {
+ /* passes ownership of tmp_error */
+ g_propagate_error(error, tmp_error);
+ goto error_cleanup;
+ }
+ g_error_free(tmp_error);
+
+ /*
+ * Macro returned - handle like regular
+ * end of macro, even though some checks
+ * are unnecessary here.
+ * macro_pc will still point to the return PC.
+ */
+ g_assert(macro_machine.parent.current == &teco_state_start);
+
+ /*
+ * Discard all braces, except the current one.
+ */
+ if (!teco_expressions_brace_return(parent_brace_level, teco_error_return_args, error))
+ goto error_cleanup;
+
+ /*
+ * Clean up the loop stack.
+ * We are allowed to return in loops.
+ * NOTE: This does not have to be undone.
+ */
+ g_array_remove_range(teco_loop_stack, macro_machine.loop_stack_fp,
+ teco_loop_stack->len - macro_machine.loop_stack_fp);
}
- g_error_free(tmp_error);
- /*
- * Macro returned - handle like regular
- * end of macro, even though some checks
- * are unnecessary here.
- * macro_pc will still point to the return PC.
- */
- g_assert(macro_machine.parent.current == &teco_state_start);
+ if (G_LIKELY(teco_goto_backup_pc < 0))
+ break;
- /*
- * Discard all braces, except the current one.
- */
- if (!teco_expressions_brace_return(parent_brace_level, teco_error_return_args, error))
- goto error_cleanup;
+ /* continue after :Olabel$ */
+ macro_machine.macro_pc = teco_goto_backup_pc;
+ /* macro could have ended in a "lookahead" state */
+ macro_machine.parent.current = &teco_state_start;
- /*
- * Clean up the loop stack.
- * We are allowed to return in loops.
- * NOTE: This does not have to be undone.
- */
- g_array_remove_range(teco_loop_stack, macro_machine.loop_stack_fp,
- teco_loop_stack->len - macro_machine.loop_stack_fp);
+ 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 (macro_machine.parent.must_undo)
+ teco_undo_flags(macro_machine.flags);
+ macro_machine.flags.mode = TECO_MODE_NORMAL;
+
+ /* no need to reparse everything in the future */
+ macro_machine.goto_table.complete = TRUE;
}
if (G_UNLIKELY(teco_goto_skip_label.len > 0)) {
- g_autofree gchar *label_printable = teco_string_echo(teco_goto_skip_label.data, teco_goto_skip_label.len);
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Label \"%s\" not found", label_printable);
+ teco_error_label_set(error, teco_goto_skip_label.data, teco_goto_skip_label.len);
goto error_attach;
}
diff --git a/src/undo.c b/src/undo.c
index c8b22ab..2048af3 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -34,6 +34,7 @@ TECO_DEFINE_UNDO_SCALAR(gunichar);
TECO_DEFINE_UNDO_SCALAR(gint);
TECO_DEFINE_UNDO_SCALAR(guint);
TECO_DEFINE_UNDO_SCALAR(gsize);
+TECO_DEFINE_UNDO_SCALAR(gssize);
TECO_DEFINE_UNDO_SCALAR(teco_int_t);
TECO_DEFINE_UNDO_SCALAR(gboolean);
TECO_DEFINE_UNDO_SCALAR(gconstpointer);
diff --git a/src/undo.h b/src/undo.h
index 459fdb0..c236970 100644
--- a/src/undo.h
+++ b/src/undo.h
@@ -182,6 +182,9 @@ TECO_DECLARE_UNDO_SCALAR(guint);
TECO_DECLARE_UNDO_SCALAR(gsize);
#define teco_undo_gsize(VAR) (*teco_undo_object_gsize_push(&(VAR)))
+TECO_DECLARE_UNDO_SCALAR(gssize);
+#define teco_undo_gssize(VAR) (*teco_undo_object_gssize_push(&(VAR)))
+
TECO_DECLARE_UNDO_SCALAR(teco_int_t);
#define teco_undo_int(VAR) (*teco_undo_object_teco_int_t_push(&(VAR)))
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 3cca113..0079c38 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -117,6 +117,7 @@ AT_CLEANUP
AT_SETUP([Gotos and labels])
TE_CHECK([[@O//]], 1, ignore, ignore)
TE_CHECK([[^^XUq @O/s.^EUq/ (0/0) !s.X!]], 0, ignore, ignore)
+TE_CHECK([[:@O/foo/]], 0, ignore, ignore)
TE_CHECK([[1@O/foo,bar/ (0/0) !bar!]], 0, ignore, ignore)
# No-op gotos
TE_CHECK([[-1@O/foo/ 1@O/foo/ @O/,foo/]], 0, ignore, ignore)