diff options
-rw-r--r-- | doc/sciteco.7.template | 10 | ||||
-rw-r--r-- | src/core-commands.c | 61 | ||||
-rw-r--r-- | tests/testsuite.at | 29 |
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) |