/** * @file read_data.c * @brief Tests READ, DATA and RESTORE against the pre-scanned item list. * * The reference's READ does not read: it records its identifiers, sets the scope * waiting for a DATA verb, and lets execution skip forward until one turns up. * Two things follow, and both are asserted here as the defects they were -- * TODO.md section 6. */ #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 The ordinary case: DATA below its READ, filled in order. */ static void test_read_in_order(void) { TEST_REQUIRE_OK(run_program("10 READ A$, B#\n" "20 DATA \"HELLO\", 12345\n" "30 PRINT A$\n" "40 PRINT B#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "HELLO\n12345\n"); harness_stop(); } /** * @brief A DATA line *above* its READ is found. * * It was not: skipping forward from the READ ran off the end of the program in * silence, and a second READ re-read the same line. */ static void test_data_before_read(void) { TEST_REQUIRE_OK(run_program("10 DATA 1, 2\n" "20 READ A#, B#\n" "30 PRINT A#\n" "40 PRINT B#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n2\n"); harness_stop(); } /** * @brief Lines between a READ and its DATA are not skipped. * * They were, because the skip-forward *was* the implementation. A C128 runs * them: READ takes the next item and execution carries on normally. */ static void test_no_skipping(void) { TEST_REQUIRE_OK(run_program("10 READ A#\n" "20 PRINT \"BETWEEN\"\n" "30 DATA 5\n" "40 PRINT A#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "BETWEEN\n5\n"); harness_stop(); } /** @brief Successive READs walk the cursor along rather than starting over. */ static void test_cursor_advances(void) { TEST_REQUIRE_OK(run_program("10 DATA 1, 2, 3\n" "20 READ A#\n" "30 READ B#\n" "40 READ C#\n" "50 PRINT A# + B# + C#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "6\n"); harness_stop(); } /** @brief Bare RESTORE goes back to the first item. */ static void test_restore(void) { TEST_REQUIRE_OK(run_program("10 DATA 1, 2, 3\n" "20 READ A#\n" "30 RESTORE\n" "40 READ B#\n" "50 PRINT A#\n" "60 PRINT B#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "1\n1\n"); harness_stop(); } /** @brief RESTORE (line) starts again at the first item on or after that line. */ static void test_restore_line(void) { TEST_REQUIRE_OK(run_program("10 DATA 1, 2\n" "20 DATA 30, 40\n" "30 RESTORE 20\n" "40 READ A#, B#\n" "50 PRINT A#\n" "60 PRINT B#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "30\n40\n"); harness_stop(); /* * A line with no DATA on it finds the next one that has some -- a program * names the line it wants to read *from*, not one that must hold items. */ TEST_REQUIRE_OK(run_program("10 DATA 1, 2\n" "20 REM NOTHING HERE\n" "30 DATA 99\n" "40 RESTORE 20\n" "50 READ A#\n" "60 PRINT A#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "99\n"); harness_stop(); } /** @brief Reading past the last item is reported rather than yielding zeroes. */ static void test_out_of_data(void) { TEST_REQUIRE_OK(run_program("10 DATA 1\n" "20 READ A#, B#\n" "30 PRINT \"UNREACHED\"\n")); TEST_REQUIRE(strstr(HARNESS_OUTPUT, "OUT OF DATA") != NULL, "reading past the end should say so, got \"%s\"", HARNESS_OUTPUT); TEST_REQUIRE(strstr(HARNESS_OUTPUT, "UNREACHED") == NULL, "the program should have stopped, got \"%s\"", HARNESS_OUTPUT); harness_stop(); } /** @brief A quoted item read into a number is a mistake worth reporting. */ static void test_type_mismatch(void) { TEST_REQUIRE_OK(run_program("10 DATA \"TEXT\"\n" "20 READ A#\n")); TEST_REQUIRE(strstr(HARNESS_OUTPUT, "is a string") != NULL, "a quoted item read into a number should be refused, got \"%s\"", HARNESS_OUTPUT); harness_stop(); /* An unquoted number read into a string is fine: it becomes its text. */ TEST_REQUIRE_OK(run_program("10 DATA 42\n" "20 READ A$\n" "30 PRINT A$\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "42\n"); harness_stop(); } /** @brief A quoted item may hold commas and colons; that is what quotes are for. */ static void test_quoted_items(void) { TEST_REQUIRE_OK(run_program("10 DATA \"ONE, TWO\", \"A:B\"\n" "20 READ A$, B$\n" "30 PRINT A$\n" "40 PRINT B$\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONE, TWO\nA:B\n"); harness_stop(); } /** @brief A colon ends a DATA statement, as it does on a Commodore. */ static void test_colon_ends_data(void) { TEST_REQUIRE_OK(run_program("10 DATA 1, 2 : PRINT \"AFTER DATA\"\n" "20 READ A#, B#\n" "30 PRINT A# + B#\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "AFTER DATA\n3\n"); harness_stop(); } /** @brief A float item fills a float variable with its fractional part intact. */ static void test_float_items(void) { TEST_REQUIRE_OK(run_program("10 DATA 1.5\n" "20 READ A%\n" "30 PRINT A%\n")); TEST_REQUIRE_STR(HARNESS_OUTPUT, "1.500000\n"); harness_stop(); } int main(void) { test_read_in_order(); test_data_before_read(); test_no_skipping(); test_cursor_advances(); test_restore(); test_restore_line(); test_out_of_data(); test_type_mismatch(); test_quoted_items(); test_colon_ends_data(); test_float_items(); return akbasic_test_failures; }