aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2022-12-10 07:56:44 +0300
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2022-12-10 07:56:44 +0300
commit247884ab43025408c9f717ce2f15b6b725f74159 (patch)
treeca63cf1cf9f924bf415bfc793e64006941df9e7d
parent351d07a7f79049a92a35feb588f13a571d6d1c89 (diff)
downloadsciteco-247884ab43025408c9f717ce2f15b6b725f74159.tar.gz
fixed pass-through loops: especially :> and :F<
* fixes test cases like 3<%a:> * you can now use :F< in pass-through loops as well * F> outside of loops will now exit the current macro level. This is analogous to what TECO-11 did. In interactive mode, F> is currently also equivalent to $$ (terminates command line).
-rw-r--r--doc/sciteco.7.template10
-rw-r--r--src/core-commands.c61
-rw-r--r--tests/testsuite.at29
3 files changed, 80 insertions, 20 deletions
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index dbd9392..76c32bd 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -1982,9 +1982,9 @@ from it manually.
Consequently, if \fIn\fP is omitted the loop will be an
infinite one.
.LP
-Additionally \*(ST supports special colon-modified forms
-of the loop start and end commands for processing the
-argument stack dynamically.
+Additionally \*(ST supports special colon-modified
+\(lqpass-through\(rq forms of the loop start and end commands
+for processing the argument stack dynamically.
.SCITECO_TOPIC :>
The \fB:>\fP loop end command will \fInot\fP pop values left
on the stack since the beginning of the loop and can be used
@@ -1996,8 +1996,8 @@ For instance, the following command will leave the numbers
0Ua 5<%a:>
.SCITECO_TT_END
.EE
-The command can be understood as an aggregated form of the
-expression \(lq(%a,%a,%a,%a,%a)\(rq.
+The command can be understood as equivalent to the expressions
+\(lq(%a)(%a)(%a)(%a)(%a)\(rq or \(lq(%a,%a,%a,%a,%a)\(rq.
.SCITECO_TOPIC :<
Consequently, the colon-modified loop start command will
\fInot\fP represent an argument barrier and the corresponding
diff --git a/src/core-commands.c b/src/core-commands.c
index 5f6ec53..81bc1fe 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -278,7 +278,8 @@ teco_state_start_loop_close(teco_machine_main_t *ctx, GError **error)
return;
}
- teco_loop_context_t *lctx = &g_array_index(teco_loop_stack, teco_loop_context_t, teco_loop_stack->len-1);
+ teco_loop_context_t *lctx = &g_array_index(teco_loop_stack, teco_loop_context_t,
+ teco_loop_stack->len-1);
gboolean colon_modified = teco_machine_main_eval_colon(ctx);
/*
@@ -289,19 +290,18 @@ teco_state_start_loop_close(teco_machine_main_t *ctx, GError **error)
*/
if (!lctx->pass_through) {
if (colon_modified) {
- if (!teco_expressions_eval(FALSE, error))
+ if (!teco_expressions_brace_close(error))
return;
- teco_expressions_push_op(TECO_OP_NEW);
- } else if (!teco_expressions_discard_args(error)) {
+ if (lctx->counter != 1)
+ teco_expressions_brace_open();
+ } else if (!teco_expressions_discard_args(error) ||
+ (lctx->counter == 1 && !teco_expressions_brace_close(error))) {
return;
}
}
if (lctx->counter == 1) {
/* this was the last loop iteration */
- if (!lctx->pass_through &&
- !teco_expressions_brace_close(error))
- return;
undo__insert_val__teco_loop_stack(teco_loop_stack->len-1, *lctx);
g_array_remove_index(teco_loop_stack, teco_loop_stack->len-1);
} else {
@@ -1251,26 +1251,47 @@ TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_start,
/*$ F<
* F< -- Go to loop start or jump to beginning of macro
+ * :F<
*
* Immediately jumps to the current loop's start.
* Also works from inside conditionals.
*
+ * This command behaves exactly like \fB>\fP with regard to
+ * colon-modifiers.
+ *
* Outside of loops \(em or in a macro without
* a loop \(em this jumps to the beginning of the macro.
*/
static void
teco_state_fcommand_loop_start(teco_machine_main_t *ctx, GError **error)
{
- /* FIXME: what if in brackets? */
- if (!teco_expressions_discard_args(error))
+ if (teco_loop_stack->len <= ctx->loop_stack_fp) {
+ /* outside of loop */
+ if (!teco_expressions_discard_args(error))
+ return;
+ ctx->macro_pc = -1;
return;
+ }
+
+ teco_loop_context_t *lctx = &g_array_index(teco_loop_stack, teco_loop_context_t,
+ teco_loop_stack->len-1);
+ gboolean colon_modified = teco_machine_main_eval_colon(ctx);
+
+ if (!lctx->pass_through) {
+ if (colon_modified) {
+ if (!teco_expressions_brace_close(error))
+ return;
+ teco_expressions_brace_open();
+ } else if (!teco_expressions_discard_args(error)) {
+ return;
+ }
+ }
- ctx->macro_pc = teco_loop_stack->len > ctx->loop_stack_fp
- ? g_array_index(teco_loop_stack, teco_loop_context_t, teco_loop_stack->len-1).pc : -1;
+ ctx->macro_pc = lctx->pc;
}
/*$ F> continue
- * F> -- Go to loop end
+ * F> -- Go to loop end or return from macro
* :F>
*
* Jumps to the current loop's end.
@@ -1285,15 +1306,25 @@ teco_state_fcommand_loop_start(teco_machine_main_t *ctx, GError **error)
* be exited, you can type in the loop's remaining commands
* without them being executed (but they are parsed).
*
- * When colon-modified, \fB:F>\fP behaves like \fB:>\fP
- * and allows numbers to be aggregated on the stack.
+ * This command behaves exactly like \fB>\fP with regard to
+ * colon-modifiers.
*
* Calling \fBF>\fP outside of a loop at the current
- * macro invocation level will throw an error.
+ * macro invocation level is equivalent to calling <$$>
+ * (terminate command line or return from macro).
*/
static void
teco_state_fcommand_loop_end(teco_machine_main_t *ctx, GError **error)
{
+ if (teco_loop_stack->len <= ctx->loop_stack_fp) {
+ /* outside of loop */
+ ctx->parent.current = &teco_state_start;
+ if (!teco_expressions_eval(FALSE, error))
+ return;
+ teco_error_return_set(error, teco_expressions_args());
+ return;
+ }
+
guint old_len = teco_loop_stack->len;
/*
diff --git a/tests/testsuite.at b/tests/testsuite.at
index e604c7f..abb3aa6 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -16,6 +16,17 @@ AT_COLOR_TESTS
AT_BANNER([Features])
+AT_SETUP([Number stack])
+AT_CHECK([$SCITECO -e "2%a,%a - 3\"N(0/0)'"], 0, ignore, ignore)
+# It's not quite clear what would be the best semantics for comma:
+# a) Superfluous commas as in ",," or "(1,)" should be an error.
+# b) Superfluous commas should be ignored which is effectively what we do now.
+# Even then it might be advisable to treat (1,) like (1).
+# c) The empty "list" element is equivalent to 0, so
+# "1,,2" is equivalent to "1,0,2" and (1,) to (1,0).
+AT_CHECK([$SCITECO -e "(1,) \"~|(0/0)'"], 0, ignore, ignore)
+AT_CLEANUP
+
AT_SETUP([Missing left operand])
AT_CHECK([$SCITECO -e '+23='], 1, ignore, ignore)
AT_CLEANUP
@@ -24,6 +35,15 @@ AT_SETUP([Closing loops at the correct macro level])
AT_CHECK([$SCITECO -e '@^Ua{>} <Ma'], 1, ignore, ignore)
AT_CLEANUP
+AT_SETUP([Pass-through loops])
+# NOTE: This requires the <=>, so that values get consumed from the stack.
+# More elegant would be a command for popping exactly one argument like <:$>.
+AT_CHECK([$SCITECO -e "1,2,3,-1:<\"~1;'%a=> Qa-6\"N(0/0)'"], 0, ignore, ignore)
+AT_CHECK([$SCITECO -e "1,2,3,-1:<\"~1;'%a= F>(0/0)> Qa-6\"N(0/0)'"], 0, ignore, ignore)
+AT_CHECK([$SCITECO -e "3<%a:>-3\"N(0/0)'"], 0, ignore, ignore)
+AT_CHECK([$SCITECO -e "3<%a :F>(0/0):>-3\"N(0/0)'"], 0, ignore, ignore)
+AT_CLEANUP
+
AT_SETUP([String arguments])
AT_CHECK([$SCITECO -e $'Ifoo^Q\e(0/0)\e'], 0, ignore, ignore)
AT_CHECK([$SCITECO -e '@I"foo^Q"(0/0)"'], 0, ignore, ignore)
@@ -113,6 +133,15 @@ AT_CLEANUP
AT_BANNER([Known Bugs])
+AT_SETUP([Number stack])
+# Nobody needs the current semantic of digit "commands" and they
+# will be replaced with proper number parser states, which will also allow for
+# floating point constants.
+# With the current parser, it is hard to even interpret the following code correctly...
+AT_CHECK([$SCITECO -e "(12)3 - 3\"N(0/0)'"], 0, ignore, ignore)
+AT_XFAIL_IF(true)
+AT_CLEANUP
+
AT_SETUP([Dangling Else/End-If])
AT_CHECK([$SCITECO -e "'"], 1, ignore, ignore)
AT_CHECK([$SCITECO -e "| (0/0) '"], 1, ignore, ignore)