/** * @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 #include #include #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; }