244 lines
8.9 KiB
C
244 lines
8.9 KiB
C
|
|
/**
|
||
|
|
* @file renumber.c
|
||
|
|
* @brief Tests RENUMBER: the lines move, and every reference to one moves with it.
|
||
|
|
*
|
||
|
|
* The reason this verb was deferred rather than written early is that the easy
|
||
|
|
* half is worthless on its own. Moving lines is a permutation; a RENUMBER that
|
||
|
|
* did only that would silently break every branch in the program. So most of
|
||
|
|
* what is asserted here is the *rewriting*, including the two things a textual
|
||
|
|
* rewrite can get wrong: a number inside a string literal, and a target naming a
|
||
|
|
* line that does not exist.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
#include <akbasic/error.h>
|
||
|
|
#include <akbasic/runtime.h>
|
||
|
|
|
||
|
|
#include "harness.h"
|
||
|
|
#include "testutil.h"
|
||
|
|
|
||
|
|
/** @brief Load a program without running it. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *load(const char *source)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
PASS(errctx, harness_start(NULL));
|
||
|
|
PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Run whatever is loaded, bounded so a broken branch fails rather than hangs. */
|
||
|
|
static akerr_ErrorContext AKERR_NOIGNORE *run_loaded(void)
|
||
|
|
{
|
||
|
|
PREPARE_ERROR(errctx);
|
||
|
|
|
||
|
|
HARNESS_RUNTIME.environment->nextline = 0;
|
||
|
|
PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
|
||
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 40000));
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief Lines land on the numbers asked for, in source order. */
|
||
|
|
static void test_lines_move(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 PRINT \"A\"\n"
|
||
|
|
"7 PRINT \"B\"\n"
|
||
|
|
"9 PRINT \"C\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"A\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "PRINT \"B\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[30].code, "PRINT \"C\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[5].code, "");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/* A different start and increment. */
|
||
|
|
TEST_REQUIRE_OK(load("1 PRINT \"A\"\n2 PRINT \"B\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 100, 5, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "PRINT \"A\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[105].code, "PRINT \"B\"");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief GOTO and GOSUB targets follow the lines they name. */
|
||
|
|
static void test_branches_rewritten(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 GOTO 9\n"
|
||
|
|
"7 PRINT \"SKIPPED\"\n"
|
||
|
|
"9 PRINT \"ARRIVED\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 30");
|
||
|
|
TEST_REQUIRE_OK(run_loaded());
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ARRIVED\n");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(load("5 GOSUB 9\n"
|
||
|
|
"6 END\n"
|
||
|
|
"9 PRINT \"SUB\"\n"
|
||
|
|
"11 RETURN\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOSUB 30");
|
||
|
|
TEST_REQUIRE_OK(run_loaded());
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "SUB\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A GOTO after THEN or ELSE is rewritten; it is not at a statement start. */
|
||
|
|
static void test_branch_inside_a_condition(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 IF 1 == 1 THEN GOTO 9 ELSE GOTO 7\n"
|
||
|
|
"7 PRINT \"ELSE\"\n"
|
||
|
|
"9 PRINT \"THEN\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "IF 1 == 1 THEN GOTO 30 ELSE GOTO 20");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief ON's whole target list is rewritten, not just the first entry. */
|
||
|
|
static void test_on_list_rewritten(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 ON 2 GOTO 7, 9\n"
|
||
|
|
"7 PRINT \"ONE\"\n"
|
||
|
|
"9 PRINT \"TWO\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
/* The original spacing survives; only the numbers change. */
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "ON 2 GOTO 20, 30");
|
||
|
|
TEST_REQUIRE_OK(run_loaded());
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "TWO\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief RESTORE, TRAP and COLLISION targets are rewritten too. */
|
||
|
|
static void test_other_verbs_rewritten(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 RESTORE 9\n"
|
||
|
|
"7 TRAP 9\n"
|
||
|
|
"8 COLLISION 1, 9\n"
|
||
|
|
"9 DATA 1\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "RESTORE 40");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "TRAP 40");
|
||
|
|
/* COLLISION's handler is its *second* argument, so the type is left alone. */
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[30].code, "COLLISION 1, 40");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A number inside a string literal is not a line reference.
|
||
|
|
*
|
||
|
|
* The one thing a textual rewrite is most likely to get wrong, and the reason
|
||
|
|
* the rewrite has to understand quotes at all.
|
||
|
|
*/
|
||
|
|
static void test_string_literals_untouched(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 PRINT \"GOTO 9 IS TEXT\"\n"
|
||
|
|
"9 PRINT \"END\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"GOTO 9 IS TEXT\"");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief A target naming no line is left alone rather than invented.
|
||
|
|
*
|
||
|
|
* `GOTO 9999` in a program with no line 9999 is already broken; rewriting it to
|
||
|
|
* point somewhere would hide that rather than fix it.
|
||
|
|
*/
|
||
|
|
static void test_dangling_target_left_alone(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 GOTO 9999\n"
|
||
|
|
"9 PRINT \"HERE\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 9999");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A label target needs no rewriting, which is the case for using labels. */
|
||
|
|
static void test_labels_need_no_rewriting(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("5 GOTO DONE\n"
|
||
|
|
"7 PRINT \"SKIPPED\"\n"
|
||
|
|
"9 LABEL DONE\n"
|
||
|
|
"11 PRINT \"ARRIVED\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO DONE");
|
||
|
|
TEST_REQUIRE_OK(run_loaded());
|
||
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ARRIVED\n");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief RENUMBER from a line leaves everything before it where it was. */
|
||
|
|
static void test_partial_renumber(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("10 PRINT \"KEPT\"\n"
|
||
|
|
"55 PRINT \"MOVED\"\n"
|
||
|
|
"57 PRINT \"ALSO MOVED\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 100, 10, 50));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"KEPT\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "PRINT \"MOVED\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[110].code, "PRINT \"ALSO MOVED\"");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A renumbering that would land on a kept line is refused, not applied. */
|
||
|
|
static void test_collision_refused(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("10 PRINT \"KEPT\"\n"
|
||
|
|
"55 PRINT \"MOVED\"\n"));
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 50), AKBASIC_ERR_VALUE);
|
||
|
|
/* And nothing moved: the map is built and checked before anything is written. */
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"KEPT\"");
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[55].code, "PRINT \"MOVED\"");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief A non-positive increment is refused rather than looping forever. */
|
||
|
|
static void test_bad_arguments(void)
|
||
|
|
{
|
||
|
|
TEST_REQUIRE_OK(load("10 PRINT \"A\"\n"));
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, 0, 0), AKBASIC_ERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 10, -1, 0), AKBASIC_ERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_renumber(&HARNESS_RUNTIME, 0, 10, 0), AKBASIC_ERR_VALUE);
|
||
|
|
TEST_REQUIRE_STATUS(akbasic_renumber(NULL, 10, 10, 0), AKERR_NULLPOINTER);
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** @brief The verb reaches all of it, with its defaults. */
|
||
|
|
static void test_verb(void)
|
||
|
|
{
|
||
|
|
akbasic_ASTLeaf *leaf = NULL;
|
||
|
|
akbasic_Value *out = NULL;
|
||
|
|
|
||
|
|
TEST_REQUIRE_OK(load("5 GOTO 9\n7 PRINT \"SKIPPED\"\n9 PRINT \"ARRIVED\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
||
|
|
TEST_REQUIRE_OK(harness_parse("RENUMBER", &leaf));
|
||
|
|
TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "GOTO 30");
|
||
|
|
harness_stop();
|
||
|
|
|
||
|
|
/* And with arguments. */
|
||
|
|
TEST_REQUIRE_OK(load("5 GOTO 9\n9 PRINT \"X\"\n"));
|
||
|
|
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
|
||
|
|
TEST_REQUIRE_OK(harness_parse("RENUMBER 100, 5", &leaf));
|
||
|
|
TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
|
||
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "GOTO 105");
|
||
|
|
harness_stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
int main(void)
|
||
|
|
{
|
||
|
|
test_lines_move();
|
||
|
|
test_branches_rewritten();
|
||
|
|
test_branch_inside_a_condition();
|
||
|
|
test_on_list_rewritten();
|
||
|
|
test_other_verbs_rewritten();
|
||
|
|
test_string_literals_untouched();
|
||
|
|
test_dangling_target_left_alone();
|
||
|
|
test_labels_need_no_rewriting();
|
||
|
|
test_partial_renumber();
|
||
|
|
test_collision_refused();
|
||
|
|
test_bad_arguments();
|
||
|
|
test_verb();
|
||
|
|
return akbasic_test_failures;
|
||
|
|
}
|