104 lines
3.7 KiB
C
104 lines
3.7 KiB
C
|
|
/**
|
||
|
|
* @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 <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#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;
|
||
|
|
}
|