Give a loaded line a number when it arrives without one
A script written against LABEL and GOTO NAME never names a line number, so
the numbers it had to carry were decoration. akbasic_runtime_load(),
RUNSTREAM and DLOAD now file an unnumbered line one slot after the last one
filed; a numbered line is filed under its number and moves the cursor, so
the two mix. akbasic_runtime_file_line() is the one implementation of that
rule, so the three paths cannot drift.
The prompt is untouched. A line typed without a number is still direct mode
and still runs now -- that is the only thing separating program text from a
statement at a REPL, and it is why this is a loading feature.
What this replaces was silent data loss: an unnumbered line was filed under
the cursor unchanged, on top of the line before it. A blank line therefore
erased whatever preceded it, and RUNSTREAM did not skip blank lines the way
the other two paths did.
That moves one golden file, and the reference had the same defect.
language/arithmetic/integer.bas has four PRINT statements, an expectation
with three values, and a trailing blank line that erased 40 PRINT 4 - 2
before the program ran. The expectation is now 4 4 2 2.
tests/reference/README.md records the divergence and TODO.md section 5 item
63 says why.
akbasic_SourceLine grows a `numbered` flag so an assigned number can be told
from a written one. RENUMBER sets it on every line it touches; NEW, DELETE
and DLOAD clear it. hadlinenumber moves to akbasic_scanner_scan(), so it
always describes the line just scanned rather than only the REPL's.
Two lines carrying the same written number still keep the last, as they
always have. That is a separate decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:33:27 -04:00
|
|
|
/**
|
|
|
|
|
* @file unnumbered.c
|
|
|
|
|
* @brief Programs loaded without line numbers.
|
|
|
|
|
*
|
|
|
|
|
* A script written against `LABEL` and `GOTO NAME` never names a line number, so
|
|
|
|
|
* the numbers it used to be forced to carry were decoration. A loaded line that
|
|
|
|
|
* arrives without one now gets the slot after the last line filed, which is the
|
|
|
|
|
* rule `akbasic_runtime_file_line()` holds for all three loading paths.
|
|
|
|
|
*
|
|
|
|
|
* What is asserted here is the seam, not the language: which slot a line lands
|
|
|
|
|
* in, that both spellings execute identically, that the prompt's rule is
|
|
|
|
|
* untouched, and that a collision is refused rather than losing a line.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include <akbasic/error.h>
|
|
|
|
|
#include <akbasic/runtime.h>
|
|
|
|
|
|
|
|
|
|
#include "harness.h"
|
|
|
|
|
#include "testutil.h"
|
|
|
|
|
|
|
|
|
|
/** @brief Load a program from a string and run it to completion. */
|
|
|
|
|
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 Read a program through the sink, the way the file driver does. */
|
|
|
|
|
static akerr_ErrorContext AKERR_NOIGNORE *runstream_program(const char *source)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
PASS(errctx, harness_start(source));
|
|
|
|
|
PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUNSTREAM));
|
|
|
|
|
PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 0));
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A program with no numbers at all runs top to bottom. */
|
|
|
|
|
static void test_runs_in_order(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"ONE\"\n"
|
|
|
|
|
"PRINT \"TWO\"\n"
|
|
|
|
|
"PRINT \"THREE\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONE\nTWO\nTHREE\n");
|
|
|
|
|
|
|
|
|
|
/* Slot 0 is left empty and the program starts at 1. */
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[0].code, "");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[1].code, "PRINT \"ONE\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[2].code, "PRINT \"TWO\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[3].code, "PRINT \"THREE\"");
|
|
|
|
|
|
|
|
|
|
/* And every one of them knows it was assigned rather than written. */
|
|
|
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.source[1].numbered, "line 1 should not be marked numbered");
|
|
|
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.source[3].numbered, "line 3 should not be marked numbered");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Two unnumbered lines in a row are two lines.
|
|
|
|
|
*
|
|
|
|
|
* This is the defect the feature replaces, stated on its own: the second used to
|
|
|
|
|
* be filed under the cursor unchanged, on top of the first, with no error and no
|
|
|
|
|
* output. A test that only checked the *last* line would have passed throughout.
|
|
|
|
|
*/
|
|
|
|
|
static void test_second_line_does_not_replace_the_first(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"FIRST\"\n"
|
|
|
|
|
"PRINT \"SECOND\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "FIRST\nSECOND\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A blank line is whitespace, and costs no slot. */
|
|
|
|
|
static void test_blank_lines_cost_nothing(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"ONE\"\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"PRINT \"TWO\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONE\nTWO\n");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[2].code, "PRINT \"TWO\"");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief A numbered line moves the cursor; unnumbered ones follow from there.
|
|
|
|
|
*
|
|
|
|
|
* Execution order is slot order, not file order, which is the whole reason the
|
|
|
|
|
* two can be mixed at all: `50 PRINT "C"` runs before `100 PRINT "A"` however
|
|
|
|
|
* they were written down.
|
|
|
|
|
*/
|
|
|
|
|
static void test_mixed_numbering(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("100 PRINT \"A\"\n"
|
|
|
|
|
"PRINT \"B\"\n"
|
|
|
|
|
"50 PRINT \"C\"\n"
|
|
|
|
|
"PRINT \"D\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "C\nD\nA\nB\n");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[50].code, "PRINT \"C\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[51].code, "PRINT \"D\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[100].code, "PRINT \"A\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[101].code, "PRINT \"B\"");
|
|
|
|
|
|
|
|
|
|
/* The written numbers are marked as such; the two that followed are not. */
|
|
|
|
|
TEST_REQUIRE(HARNESS_RUNTIME.source[50].numbered, "line 50 was written and should be marked");
|
|
|
|
|
TEST_REQUIRE(HARNESS_RUNTIME.source[100].numbered, "line 100 was written and should be marked");
|
|
|
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.source[51].numbered, "line 51 was assigned, not written");
|
|
|
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.source[101].numbered, "line 101 was assigned, not written");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief Branching by label needs no numbers anywhere. */
|
|
|
|
|
static void test_label_branching(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("GOSUB GREET\n"
|
|
|
|
|
"GOTO DONE\n"
|
|
|
|
|
"PRINT \"NOT REACHED\"\n"
|
|
|
|
|
"LABEL GREET\n"
|
|
|
|
|
"PRINT \"HELLO\"\n"
|
|
|
|
|
"RETURN\n"
|
|
|
|
|
"LABEL DONE\n"
|
|
|
|
|
"PRINT \"BYE\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "HELLO\nBYE\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief A collision involving an assigned number is refused, not overwritten.
|
|
|
|
|
*
|
|
|
|
|
* `1 PRINT "C"` names a slot two unnumbered lines have already been given. Losing
|
|
|
|
|
* one of them to that is not recoverable, so the load fails instead.
|
|
|
|
|
*/
|
|
|
|
|
static void test_collision_is_refused(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
|
|
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_load(&HARNESS_RUNTIME,
|
|
|
|
|
"PRINT \"A\"\n"
|
|
|
|
|
"PRINT \"B\"\n"
|
|
|
|
|
"1 PRINT \"C\"\n"),
|
|
|
|
|
AKBASIC_ERR_BOUNDS);
|
|
|
|
|
harness_stop();
|
|
|
|
|
|
|
|
|
|
/* The mirror image: an assigned line landing on one already written. */
|
|
|
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
|
|
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_load(&HARNESS_RUNTIME,
|
|
|
|
|
"2 PRINT \"A\"\n"
|
|
|
|
|
"1 PRINT \"B\"\n"
|
|
|
|
|
"PRINT \"C\"\n"),
|
|
|
|
|
AKBASIC_ERR_BOUNDS);
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief Two lines carrying the same written number still keep the last. */
|
|
|
|
|
static void test_duplicate_written_numbers_still_replace(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("10 PRINT \"OLD\"\n"
|
|
|
|
|
"10 PRINT \"NEW\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "NEW\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief The same program through the sink lands in the same slots. */
|
|
|
|
|
static void test_runstream_assigns_the_same_slots(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(runstream_program("PRINT \"ONE\"\n"
|
|
|
|
|
"PRINT \"TWO\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONE\nTWO\n");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[1].code, "PRINT \"ONE\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[2].code, "PRINT \"TWO\"");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief A file ending in a blank line keeps its last line.
|
|
|
|
|
*
|
|
|
|
|
* `tests/reference/language/arithmetic/integer.bas` is this shape, and both this
|
|
|
|
|
* interpreter and the reference used to erase the last line before running it --
|
|
|
|
|
* the blank line was filed under the cursor, which for a line with no number of
|
|
|
|
|
* its own is the number of the line before it.
|
|
|
|
|
*/
|
|
|
|
|
static void test_trailing_blank_line_keeps_the_last_line(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(runstream_program("PRINT \"ONE\"\n"
|
|
|
|
|
"PRINT \"LAST\"\n"
|
|
|
|
|
"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "ONE\nLAST\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief The prompt still tells a program line from a statement by its number.
|
|
|
|
|
*
|
|
|
|
|
* The one rule the feature does not touch. `PRINT 2 + 2` typed at a prompt must
|
|
|
|
|
* answer 4 rather than quietly becoming line 1 of a program.
|
|
|
|
|
*/
|
|
|
|
|
static void test_prompt_still_runs_direct_mode(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(harness_start("PRINT 2 + 2\n10 PRINT \"FILED\"\nLIST\n"));
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_REPL));
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
|
|
|
|
|
|
|
|
|
|
/* 4 came out now; the numbered line went into the program and LIST found it. */
|
|
|
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "4\n") != NULL,
|
|
|
|
|
"PRINT 2 + 2 at the prompt should have answered 4, got \"%s\"", HARNESS_OUTPUT);
|
|
|
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "10 PRINT \"FILED\"") != NULL,
|
|
|
|
|
"the numbered line should have been filed and listed, got \"%s\"", HARNESS_OUTPUT);
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[1].code, "");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 16:38:54 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Branching by number to a line nobody numbered is refused before the run.
|
|
|
|
|
*
|
|
|
|
|
* `GOTO 2` in a program with no line numbers finds the second line and branches
|
|
|
|
|
* there. The prescan says so instead, and says it before any line executes -- so
|
|
|
|
|
* the program produces the error and nothing else, not the error partway through
|
|
|
|
|
* its output.
|
|
|
|
|
*/
|
|
|
|
|
static void test_numeric_branch_to_an_assigned_line_is_refused(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"A\"\n"
|
|
|
|
|
"PRINT \"B\"\n"
|
|
|
|
|
"GOTO 2\n"));
|
|
|
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "did not number") != NULL,
|
|
|
|
|
"expected a refusal naming the unnumbered target, got \"%s\"", HARNESS_OUTPUT);
|
|
|
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "A\n") == NULL,
|
|
|
|
|
"the program should not have run at all, got \"%s\"", HARNESS_OUTPUT);
|
|
|
|
|
harness_stop();
|
|
|
|
|
|
|
|
|
|
/* The refusal names the offending line, not wherever the loader stopped. */
|
|
|
|
|
TEST_REQUIRE_OK(run_program("GOTO 4\n"
|
|
|
|
|
"PRINT \"B\"\n"
|
|
|
|
|
"PRINT \"C\"\n"
|
|
|
|
|
"PRINT \"D\"\n"
|
|
|
|
|
"PRINT \"E\"\n"));
|
|
|
|
|
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "? 1 :") != NULL,
|
|
|
|
|
"expected the error to name line 1, got \"%s\"", HARNESS_OUTPUT);
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A written number is still a legal target, in a mixed program too. */
|
|
|
|
|
static void test_numeric_branch_to_a_written_line_is_allowed(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("10 PRINT \"A\"\n"
|
|
|
|
|
"20 IF A# = 0 THEN A# = 1 : GOTO 10\n"
|
|
|
|
|
"30 PRINT \"OK\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "A\nA\nOK\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
|
|
|
|
|
/* Mixed: line 10 was written, so a branch to it stands. */
|
|
|
|
|
TEST_REQUIRE_OK(run_program("10 PRINT \"A\"\n"
|
|
|
|
|
"PRINT \"B\"\n"
|
|
|
|
|
"20 IF A# = 0 THEN A# = 1 : GOTO 10\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "A\nB\nA\nB\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A branch to an empty line is left alone, as RENUMBER leaves it alone. */
|
|
|
|
|
static void test_numeric_branch_to_an_empty_line_is_allowed(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"A\"\n"
|
|
|
|
|
"GOTO 500\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "A\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A label target never reaches the check, so it cannot be refused by it. */
|
|
|
|
|
static void test_label_targets_are_never_refused(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("GOTO DONE\n"
|
|
|
|
|
"PRINT \"SKIPPED\"\n"
|
|
|
|
|
"LABEL DONE\n"
|
|
|
|
|
"PRINT \"OK\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "OK\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @brief A number inside a string literal is not a branch target. */
|
|
|
|
|
static void test_a_number_in_a_string_is_not_a_target(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(run_program("PRINT \"A\"\n"
|
|
|
|
|
"PRINT \"GOTO 2\"\n"));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_OUTPUT, "A\nGOTO 2\n");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief RENUMBER makes a numberless program branchable by number again.
|
|
|
|
|
*
|
|
|
|
|
* It moves line 3 to line 30 *and* rewrites `GOTO 3` to `GOTO 30`, and every line
|
|
|
|
|
* it touches comes out numbered -- so the branch the check refused a moment ago
|
|
|
|
|
* is now naming a number the program owns. That is the remedy the refusal names.
|
|
|
|
|
*/
|
|
|
|
|
static void test_renumber_makes_numeric_branches_legal(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
|
|
|
|
|
"PRINT \"A\"\n"
|
|
|
|
|
"GOTO 3\n"
|
|
|
|
|
"PRINT \"B\"\n"));
|
|
|
|
|
/* Before: line 3 was assigned a number, so branching to it is refused. */
|
|
|
|
|
TEST_REQUIRE_STATUS(akbasic_runtime_check_targets(&HARNESS_RUNTIME), AKBASIC_ERR_SYNTAX);
|
|
|
|
|
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "GOTO 30");
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_runtime_check_targets(&HARNESS_RUNTIME));
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
Give a loaded line a number when it arrives without one
A script written against LABEL and GOTO NAME never names a line number, so
the numbers it had to carry were decoration. akbasic_runtime_load(),
RUNSTREAM and DLOAD now file an unnumbered line one slot after the last one
filed; a numbered line is filed under its number and moves the cursor, so
the two mix. akbasic_runtime_file_line() is the one implementation of that
rule, so the three paths cannot drift.
The prompt is untouched. A line typed without a number is still direct mode
and still runs now -- that is the only thing separating program text from a
statement at a REPL, and it is why this is a loading feature.
What this replaces was silent data loss: an unnumbered line was filed under
the cursor unchanged, on top of the line before it. A blank line therefore
erased whatever preceded it, and RUNSTREAM did not skip blank lines the way
the other two paths did.
That moves one golden file, and the reference had the same defect.
language/arithmetic/integer.bas has four PRINT statements, an expectation
with three values, and a trailing blank line that erased 40 PRINT 4 - 2
before the program ran. The expectation is now 4 4 2 2.
tests/reference/README.md records the divergence and TODO.md section 5 item
63 says why.
akbasic_SourceLine grows a `numbered` flag so an assigned number can be told
from a written one. RENUMBER sets it on every line it touches; NEW, DELETE
and DLOAD clear it. hadlinenumber moves to akbasic_scanner_scan(), so it
always describes the line just scanned rather than only the REPL's.
Two lines carrying the same written number still keep the last, as they
always have. That is a separate decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:33:27 -04:00
|
|
|
/** @brief RENUMBER numbers everything, which is what asking for it means. */
|
|
|
|
|
static void test_renumber_marks_every_line_numbered(void)
|
|
|
|
|
{
|
|
|
|
|
TEST_REQUIRE_OK(harness_start(NULL));
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME,
|
|
|
|
|
"PRINT \"ONE\"\n"
|
|
|
|
|
"PRINT \"TWO\"\n"));
|
|
|
|
|
TEST_REQUIRE(!HARNESS_RUNTIME.source[1].numbered, "line 1 starts out assigned");
|
|
|
|
|
|
|
|
|
|
TEST_REQUIRE_OK(akbasic_renumber(&HARNESS_RUNTIME, 10, 10, 0));
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[10].code, "PRINT \"ONE\"");
|
|
|
|
|
TEST_REQUIRE_STR(HARNESS_RUNTIME.source[20].code, "PRINT \"TWO\"");
|
|
|
|
|
TEST_REQUIRE(HARNESS_RUNTIME.source[10].numbered, "RENUMBER should mark line 10 numbered");
|
|
|
|
|
TEST_REQUIRE(HARNESS_RUNTIME.source[20].numbered, "RENUMBER should mark line 20 numbered");
|
|
|
|
|
harness_stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
test_runs_in_order();
|
|
|
|
|
test_second_line_does_not_replace_the_first();
|
|
|
|
|
test_blank_lines_cost_nothing();
|
|
|
|
|
test_mixed_numbering();
|
|
|
|
|
test_label_branching();
|
|
|
|
|
test_collision_is_refused();
|
|
|
|
|
test_duplicate_written_numbers_still_replace();
|
|
|
|
|
test_runstream_assigns_the_same_slots();
|
|
|
|
|
test_trailing_blank_line_keeps_the_last_line();
|
|
|
|
|
test_prompt_still_runs_direct_mode();
|
2026-08-01 16:38:54 -04:00
|
|
|
test_numeric_branch_to_an_assigned_line_is_refused();
|
|
|
|
|
test_numeric_branch_to_a_written_line_is_allowed();
|
|
|
|
|
test_numeric_branch_to_an_empty_line_is_allowed();
|
|
|
|
|
test_label_targets_are_never_refused();
|
|
|
|
|
test_a_number_in_a_string_is_not_a_target();
|
|
|
|
|
test_renumber_makes_numeric_branches_legal();
|
Give a loaded line a number when it arrives without one
A script written against LABEL and GOTO NAME never names a line number, so
the numbers it had to carry were decoration. akbasic_runtime_load(),
RUNSTREAM and DLOAD now file an unnumbered line one slot after the last one
filed; a numbered line is filed under its number and moves the cursor, so
the two mix. akbasic_runtime_file_line() is the one implementation of that
rule, so the three paths cannot drift.
The prompt is untouched. A line typed without a number is still direct mode
and still runs now -- that is the only thing separating program text from a
statement at a REPL, and it is why this is a loading feature.
What this replaces was silent data loss: an unnumbered line was filed under
the cursor unchanged, on top of the line before it. A blank line therefore
erased whatever preceded it, and RUNSTREAM did not skip blank lines the way
the other two paths did.
That moves one golden file, and the reference had the same defect.
language/arithmetic/integer.bas has four PRINT statements, an expectation
with three values, and a trailing blank line that erased 40 PRINT 4 - 2
before the program ran. The expectation is now 4 4 2 2.
tests/reference/README.md records the divergence and TODO.md section 5 item
63 says why.
akbasic_SourceLine grows a `numbered` flag so an assigned number can be told
from a written one. RENUMBER sets it on every line it touches; NEW, DELETE
and DLOAD clear it. hadlinenumber moves to akbasic_scanner_scan(), so it
always describes the line just scanned rather than only the REPL's.
Two lines carrying the same written number still keep the last, as they
always have. That is a separate decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:33:27 -04:00
|
|
|
test_renumber_marks_every_line_numbered();
|
|
|
|
|
return akbasic_test_failures;
|
|
|
|
|
}
|