/** * @file for_semantics.c * @brief Known-failing. Asserts what FOR/NEXT *should* do, which is not what it does. * * Registered in `AKBASIC_KNOWN_FAILING_TESTS`, so CTest expects it to fail. When * it starts passing, CTest reports "unexpectedly passed" and that is the cue to * move it into `AKBASIC_TESTS` along with the fix. * * Two defects, both found while writing tests/for_next.c and both recorded in * TODO.md section 6. Neither is the Go reference's fault alone -- the port * reproduced them faithfully -- and neither can be fixed without moving a golden * file, which is why they are written down rather than quietly corrected. * * **1. The body runs once with the overshot value.** The loop condition is * tested against the counter *before* the increment, so the increment's result * reaches the body before anything checks it against the limit. * `FOR I = 1 TO 9 STEP 3` runs its body with 1, 4, 7 and then 10. * * **2. The counter does not survive the loop.** It lives in the environment the * loop pushed, which pops when the loop ends, so reading it afterwards finds a * fresh variable holding zero. A C128 leaves the counter at the value that ended * the loop, and plenty of published listings read it afterwards. * * They are related but not the same fix: the first is an ordering bug in * akbasic_cmd_next(), and the second is a scoping decision about where a loop * counter is created. */ #include #include #include #include "harness.h" #include "testutil.h" /** @brief Run a program to completion in RUN mode, from a string. */ static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source) { PREPARE_ERROR(errctx); PASS(errctx, harness_start(NULL)); PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source)); PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN)); PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 0)); SUCCEED_RETURN(errctx); } /** @brief A step that overshoots the limit must not run the body again. */ static void test_step_does_not_overshoot(void) { TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 9 STEP 3\n" "20 PRINT I#\n" "30 NEXT I#\n")); /* Today: "1\n4\n7\n10\n" -- the body runs with 10, which is past the limit. */ TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n4\n7\n"); harness_stop(); TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 2 STEP 100\n" "20 PRINT I#\n" "30 NEXT I#\n")); /* Today: "1\n101\n". */ TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n"); harness_stop(); } /** @brief The loop counter is readable after the loop, holding where it stopped. */ static void test_counter_survives_the_loop(void) { TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 4\n" "20 NEXT I#\n" "30 PRINT I#\n")); /* Today: "0\n" -- the loop's environment took the variable with it. */ TEST_REQUIRE_STR(HARNESS_OUTPUT, "5\n"); harness_stop(); } /** * @brief A loop whose limit equals its start still runs its body once. * * `FOR I = 1 TO 1` executes the body one time on every BASIC there has ever * been. Here the entry test treats "the counter has reached the limit" as * "do not enter", so the body is skipped entirely -- and * tests/reference/language/flowcontrol/forloopwaitingforcommand.bas pins that, * which is why this cannot simply be corrected. */ static void test_single_iteration_loop(void) { TEST_REQUIRE_OK(run_program("10 FOR I# = 1 TO 1\n" "20 PRINT \"ONCE\"\n" "30 NEXT I#\n")); /* Today: "" -- the body never runs. */ TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONCE\n"); harness_stop(); } int main(void) { test_step_does_not_overshoot(); test_counter_survives_the_loop(); test_single_iteration_loop(); return akbasic_test_failures; }