Files
akbasic/tests/for_semantics.c
Logikoma 8a02674af5
All checks were successful
akbasic CI Build / cmake_build (push) Successful in 3m34s
akbasic CI Build / coverage (push) Successful in 4m4s
akbasic CI Build / sanitizers (push) Successful in 6m59s
akbasic CI Build / akgl_build (push) Successful in 7m57s
akbasic CI Build / mutation_test (push) Successful in 23m28s
Move BASIC fixtures into the editable language corpus
Move every program and expectation out of tests/reference and register the unified tests/language corpus as local cases. Remove the old immutable-corpus protections from build, maintenance, and documentation paths.

Co-authored-by: andrew <andrew@aklabs.net>
2026-08-04 16:22:16 -04:00

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/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;
}