aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO1
-rw-r--r--doc/sciteco.1.in4
-rw-r--r--src/cmdline.h7
-rw-r--r--src/core-commands.c224
-rw-r--r--src/expressions.c2
-rw-r--r--src/sciteco.h6
-rw-r--r--tests/testsuite.at11
7 files changed, 207 insertions, 48 deletions
diff --git a/TODO b/TODO
index 6f044b5..b2aab0f 100644
--- a/TODO
+++ b/TODO
@@ -344,6 +344,7 @@ Features:
The DEC behavior could be achieved by always searching till the end
of the buffer, but excluding all matches beyond the target range.
* ^A, :Gq, T, ^T and stdio in general
+ * :=, :==, :=== to inhibit the linefeed.
* ^W was an immediate action command to repaint the screen.
This could be a regular command to allow refreshing in long loops.
Video TECO had ET for the same purpose.
diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in
index c7ad1a9..82c3376 100644
--- a/doc/sciteco.1.in
+++ b/doc/sciteco.1.in
@@ -141,6 +141,10 @@ Batch mode does not have these restrictions.
.IP \(bu
A few commands that modify the command line are only available
in interactive mode.
+.IP \(bu
+A few commands like \fB^C\fP are disallowed in interactive mode
+when run from the command-line macro or behave slightly
+differently compared to batch mode (e.g. \fB$$\fP or \fB==\fP).
.RE
.
.LP
diff --git a/src/cmdline.h b/src/cmdline.h
index ebdf1e1..1a57db5 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -84,6 +84,13 @@ teco_cmdline_keymacro_c(gchar key, GError **error)
return TRUE;
}
+/** Check whether we are executing directly from the end of the command line. */
+static inline gboolean
+teco_cmdline_is_executing(teco_machine_main_t *ctx)
+{
+ return ctx == &teco_cmdline.machine && ctx->macro_pc == teco_cmdline.effective_len;
+}
+
extern gboolean teco_quit_requested;
/*
diff --git a/src/core-commands.c b/src/core-commands.c
index 7222a9f..2ccb8ec 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -67,6 +67,23 @@ gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_mach
##__VA_ARGS__ \
)
+/**
+ * @class TECO_DEFINE_STATE_START
+ * @implements TECO_DEFINE_STATE_COMMAND
+ * @ingroup states
+ *
+ * Base state for everything where a new command can begin
+ * (the start state itself and all lookahead states).
+ */
+#define TECO_DEFINE_STATE_START(NAME, ...) \
+ TECO_DEFINE_STATE_COMMAND(NAME, \
+ .end_of_macro_cb = NULL, /* Allowed at the end of a macro! */ \
+ .is_start = TRUE, \
+ .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE, \
+ ##__VA_ARGS__ \
+ )
+
+static teco_state_t *teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
static teco_state_t *teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
static teco_state_t *teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
@@ -543,34 +560,173 @@ teco_state_start_cmdline_pop(teco_machine_main_t *ctx, GError **error)
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CMDLINE, "");
}
-/*$ "=" print
- * <n>= -- Show value as message
+/**
+ * Print number from stack in the given radix.
+ *
+ * It must be popped manually, so we can call it multiple times
+ * on the same number.
+ */
+static gboolean
+teco_print(guint radix, GError **error)
+{
+ if (!teco_expressions_eval(FALSE, error))
+ return FALSE;
+ if (!teco_expressions_args()) {
+ teco_error_argexpected_set(error, "=");
+ return FALSE;
+ }
+ /*
+ * FIXME: There should be a raw output function,
+ * also to allow output without trailing LF.
+ * Perhaps repurpose teco_expressions_format().
+ */
+ const gchar *fmt = "%" TECO_INT_MODIFIER "d";
+ switch (radix) {
+ case 8: fmt = "%" TECO_INT_MODIFIER "o"; break;
+ case 16: fmt = "%" TECO_INT_MODIFIER "X"; break;
+ }
+ teco_interface_msg(TECO_MSG_USER, fmt, teco_expressions_peek_num(0));
+ return TRUE;
+}
+
+/*$ "=" "==" "===" print
+ * <n>= -- Print integer as message
+ * <n>==
+ * <n>===
*
* Shows integer <n> as a message in the message line and/or
* on the console.
- * It is currently always formatted as a decimal integer and
- * shown with the user-message severity.
+ * One \(lq=\(rq formats the integer as a signed decimal number,
+ * \(lq==\(rq formats as an unsigned octal number and
+ * \(lq===\(rq as an unsigned hexadecimal number.
+ * It is logged with the user-message severity.
* The command fails if <n> is not given.
+ *
+ * A noteworthy quirk is that \(lq==\(rq and \(lq===\(rq
+ * will print 2 or 3 numbers in succession when executed
+ * from interactive mode at the end of the command line
+ * in order to guarantee immediate feedback.
+ *
+ * If you want to print multiple values from the stack,
+ * you have to put the \(lq=\(rq into a pass-through loop
+ * or separate the commands with whitespace.
*/
-/**
- * @todo perhaps care about current radix
- * @todo colon-modifier to suppress line-break on console?
+/*
+ * In order to imitate TECO-11 closely, we apply the lookahead
+ * strategy -- `=` and `==` are not executed immediately but only
+ * when a non-`=` character is parsed (cf. `$$` and `^C^C`).
+ * However, this would be very annoying during interactive
+ * execution, therefore we still print the number immediately
+ * and perhaps multiple times:
+ * Typing `===` prints the number first in decimal,
+ * then octal and finally in hexadecimal.
+ * This won't happen e.g. in a loop that is closed on the command-line.
+ *
+ * FIXME: Support colon-modifier to suppress line-break on console.
*/
-static void
-teco_state_start_print(teco_machine_main_t *ctx, GError **error)
+TECO_DECLARE_STATE(teco_state_print_decimal);
+TECO_DECLARE_STATE(teco_state_print_octal);
+
+static gboolean
+teco_state_print_decimal_initial(teco_machine_main_t *ctx, GError **error)
{
- if (!teco_expressions_eval(FALSE, error))
- return;
- if (!teco_expressions_args()) {
- teco_error_argexpected_set(error, "=");
- return;
+ if (ctx->flags.mode > TECO_MODE_NORMAL || !teco_cmdline_is_executing(ctx))
+ return TRUE;
+ /*
+ * Interactive invocation:
+ * don't yet pop number as we may have to print it repeatedly
+ */
+ return teco_print(10, error);
+}
+
+static teco_state_t *
+teco_state_print_decimal_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+{
+ if (chr == '=')
+ return &teco_state_print_octal;
+
+ if (ctx->flags.mode == TECO_MODE_NORMAL) {
+ if (!teco_cmdline_is_executing(ctx) && !teco_print(10, error))
+ return NULL;
+ teco_expressions_pop_num(0);
}
- teco_int_t v;
- if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
- return;
- teco_interface_msg(TECO_MSG_USER, "%" TECO_INT_FORMAT, v);
+ return teco_state_start_input(ctx, chr, error);
+}
+
+/*
+ * Due to the deferred nature of `=`,
+ * it is valid to end in this state as well.
+ */
+static gboolean
+teco_state_print_decimal_end_of_macro(teco_machine_main_t *ctx, GError **error)
+{
+ if (teco_cmdline_is_executing(ctx) || ctx->flags.mode > TECO_MODE_NORMAL)
+ return TRUE;
+ if (!teco_print(10, error))
+ return FALSE;
+ teco_expressions_pop_num(0);
+ return TRUE;
+}
+
+TECO_DEFINE_STATE_START(teco_state_print_decimal,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_print_decimal_initial,
+ .end_of_macro_cb = (teco_state_end_of_macro_cb_t)
+ teco_state_print_decimal_end_of_macro
+);
+
+static gboolean
+teco_state_print_octal_initial(teco_machine_main_t *ctx, GError **error)
+{
+ if (ctx->flags.mode > TECO_MODE_NORMAL || !teco_cmdline_is_executing(ctx))
+ return TRUE;
+ /*
+ * Interactive invocation:
+ * don't yet pop number as we may have to print it repeatedly
+ */
+ return teco_print(8, error);
+}
+
+static teco_state_t *
+teco_state_print_octal_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+{
+ if (chr == '=') {
+ if (ctx->flags.mode == TECO_MODE_NORMAL) {
+ if (!teco_print(16, error))
+ return NULL;
+ teco_expressions_pop_num(0);
+ }
+ return &teco_state_start;
+ }
+
+ if (ctx->flags.mode == TECO_MODE_NORMAL) {
+ if (!teco_cmdline_is_executing(ctx) && !teco_print(8, error))
+ return NULL;
+ teco_expressions_pop_num(0);
+ }
+ return teco_state_start_input(ctx, chr, error);
+}
+
+/*
+ * Due to the deferred nature of `==`,
+ * it is valid to end in this state as well.
+ */
+static gboolean
+teco_state_print_octal_end_of_macro(teco_machine_main_t *ctx, GError **error)
+{
+ if (teco_cmdline_is_executing(ctx) || ctx->flags.mode > TECO_MODE_NORMAL)
+ return TRUE;
+ if (!teco_print(8, error))
+ return FALSE;
+ teco_expressions_pop_num(0);
+ return TRUE;
}
+TECO_DEFINE_STATE_START(teco_state_print_octal,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_print_octal_initial,
+ .end_of_macro_cb = (teco_state_end_of_macro_cb_t)
+ teco_state_print_octal_end_of_macro
+);
+
/*$ A
* [n]A -> code -- Get character code from buffer
* -A -> code
@@ -651,6 +807,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_colon = 1},
['X'] = {&teco_state_copytoqreg,
.modifier_at = TRUE, .modifier_colon = 1},
+ ['='] = {&teco_state_print_decimal},
/*
* Arithmetics
@@ -703,7 +860,6 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
.modifier_colon = 1},
['D'] = {&teco_state_start, teco_state_start_delete_chars,
.modifier_colon = 1},
- ['='] = {&teco_state_start, teco_state_start_print},
['A'] = {&teco_state_start, teco_state_start_get}
};
@@ -899,11 +1055,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_start,
- .end_of_macro_cb = NULL, /* Allowed at the end of a macro! */
- .is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
-);
+TECO_DEFINE_STATE_START(teco_state_start);
/*$ "F<" ":F<"
* F< -- Go to loop start or jump to beginning of macro
@@ -1787,14 +1939,8 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error)
return teco_expressions_discard_args(error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_escape,
- .end_of_macro_cb = teco_state_escape_end_of_macro,
- /*
- * The state should behave like teco_state_start
- * when it comes to function key macro masking.
- */
- .is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
+TECO_DEFINE_STATE_START(teco_state_escape,
+ .end_of_macro_cb = teco_state_escape_end_of_macro
);
/*
@@ -1827,18 +1973,8 @@ teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error)
return TRUE;
}
-TECO_DEFINE_STATE_COMMAND(teco_state_ctlc,
- .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial,
- /*
- * At the end of a macro, "return" is allowed, but basically a no-op.
- */
- .end_of_macro_cb = NULL,
- /*
- * The state should behave like teco_state_start
- * when it comes to function key macro masking.
- */
- .is_start = TRUE,
- .keymacro_mask = TECO_KEYMACRO_MASK_START | TECO_KEYMACRO_MASK_CASEINSENSITIVE
+TECO_DEFINE_STATE_START(teco_state_ctlc,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial
);
static teco_state_t *
diff --git a/src/expressions.c b/src/expressions.c
index f802c6e..ede54d2 100644
--- a/src/expressions.c
+++ b/src/expressions.c
@@ -387,7 +387,7 @@ teco_expressions_clear(void)
* @param buffer The output buffer of at least TECO_EXPRESSIONS_FORMAT_LEN characters.
* The output string will be null-terminated.
* @param number The number to format.
- * @param table The local Q-Register table that contains the appropriate radix register (^R).
+ * @param qreg The radix register (^R).
* @return A pointer into buffer to the beginning of the formatted number.
*/
gchar *
diff --git a/src/sciteco.h b/src/sciteco.h
index 2dc4749..40a3548 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -25,10 +25,12 @@
#if TECO_INTEGER == 32
typedef gint32 teco_int_t;
-#define TECO_INT_FORMAT G_GINT32_FORMAT
+#define TECO_INT_MODIFIER G_GINT32_MODIFIER
+#define TECO_INT_FORMAT G_GINT32_FORMAT
#elif TECO_INTEGER == 64
typedef gint64 teco_int_t;
-#define TECO_INT_FORMAT G_GINT64_FORMAT
+#define TECO_INT_MODIFIER G_GINT64_MODIFIER
+#define TECO_INT_FORMAT G_GINT64_FORMAT
#else
#error Invalid TECO integer storage size
#endif
diff --git a/tests/testsuite.at b/tests/testsuite.at
index a199f62..f7a8423 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -52,7 +52,7 @@ TE_CHECK([[2%a,%a - 3"N(0/0)' $]], 0, ignore, ignore)
# c) The empty "list" element is equivalent to 0, so
# "1,,2" is equivalent to "1,0,2" and (1,) to (1,0).
TE_CHECK([[(1,) "~|(0/0)']], 0, ignore, ignore)
-TE_CHECK([[1,(2)==]], 0, ignore, ignore)
+TE_CHECK([[1,(2)= =]], 0, ignore, ignore)
AT_CLEANUP
AT_SETUP([Exit status])
@@ -174,6 +174,15 @@ TE_CHECK([[(]]TE_MAXINT64[[)-(]]TE_MAXINT64[[)"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[(]]TE_MININT64[[)-(]]TE_MININT64[[)"N(0/0)']], 0, ignore, ignore)
AT_CLEANUP
+AT_SETUP([Printing numbers])
+# Must print only one hexadecimal number.
+TE_CHECK([[255===]], 0, stdout, ignore)
+AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 1], 0, ignore, ignore)
+# Will print a decimal, octal and 2 hexadecimal numbers.
+TE_CHECK_CMDLINE([[2<255===>]], 0, stdout, ignore)
+AT_FAIL_IF([test `$GREP -v "^Info:" stdout | wc -l` -ne 4], 0, ignore, ignore)
+AT_CLEANUP
+
AT_SETUP([Convert between line and glyph positions])
TE_CHECK([[@I/1^J2^J3/J 2^QC :^Q-3"N(0/0)']], 0, ignore, ignore)
AT_CLEANUP