202 lines
5.8 KiB
C
202 lines
5.8 KiB
C
|
|
/**
|
||
|
|
* @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 <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 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;
|
||
|
|
}
|