Fix five defects the port uncovered in the reference parser and scanner

Chained subtraction and exponentiation fold left instead of abandoning the
rest of the line -- `1 - 2 - 3` gave -1 and dropped the `- 3`. A unary leaf's
operand moves to .left, so a negative literal is one argument again and a
second argument no longer overwrites it. An operator in a line's final column
survives. Hex literals reach the parser whole. A leading zero is padding, not
a radix.

Item 16 stays pinned: the fix works and costs an upstream golden case, which
is a decision rather than a defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 11:35:14 -04:00
parent f24201fdef
commit 1eb8f9ffaa
14 changed files with 272 additions and 167 deletions

View File

@@ -10,72 +10,37 @@
* When one of these is fixed, this test starts passing and CTest reports
* "unexpectedly passed". That is the cue to split the fixed assertion out into
* the corresponding suite in AKBASIC_TESTS and strike the item from TODO.md
* section 12.
* section 6.
*
* Five of the six this file was written with are gone that way. What is left is
* the one whose fix is not a coding question but a decision, and it is described
* where it is asserted.
*/
#include "harness.h"
int main(void)
{
akbasic_ASTLeaf *leaf = NULL;
akbasic_Value *out = NULL;
char rendered[AKBASIC_MAX_STRING_LENGTH];
TEST_REQUIRE_OK(harness_start(NULL));
/*
* 12.12 -- subtraction stops after one operator, so `1 - 2 - 3` parses as
* `1 - 2` and abandons the rest of the line. It should fold left, the way
* addition does.
*/
TEST_REQUIRE_OK(harness_parse("A# = 1 - 2 - 3", &leaf));
TEST_REQUIRE_OK(akbasic_leaf_to_string(leaf, rendered, sizeof(rendered)));
TEST_REQUIRE_STR(rendered, "( A# (- (- 1 2) 3))");
/*
* 12.13 -- a unary-minus argument inflates the arity count, because the
* counter walks .right and a unary leaf keeps its operand there. ABS(-9)
* should be one argument.
*/
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
TEST_REQUIRE_OK(harness_parse("A# = ABS(-9)", &leaf));
TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
TEST_REQUIRE_INT(out->intval, 9);
/*
* 12.14 -- a comparison operator in the final column of a line is dropped,
* because matchNextChar returns without setting a token type when it cannot
* peek past the end.
*/
TEST_REQUIRE_OK(akbasic_scanner_scan(&HARNESS_RUNTIME, "A# =", NULL, 0));
TEST_REQUIRE_INT(HARNESS_RUNTIME.environment->nexttoken, 2);
/*
* 12.15 -- hex literals do not survive the scanner. matchNumber lets 'x'
* through but not the digits after it, so the parser's base-16 branch is
* unreachable.
*/
TEST_REQUIRE_OK(akbasic_scanner_scan(&HARNESS_RUNTIME, "PRINT 0xff", NULL, 0));
TEST_REQUIRE_STR(HARNESS_RUNTIME.environment->tokens[1].lexeme, "0xff");
/*
* 12.16 -- "Reserved word in variable name" never fires, because the lexeme
* still carries its type suffix when the verb table is searched. PRINT$
* should be refused as a variable name.
* 6.16 -- "Reserved word in variable name" never fires, because the lexeme
* still carries its type suffix when the verb table is searched, so PRINT$
* misses every table and becomes an ordinary string variable.
*
* Still here rather than fixed, and the reason is worth knowing before
* anybody "just fixes it": stripping the suffix before the lookup is four
* lines and it works, and it costs a golden case. The reference's own
* examples/strreverse.bas names a variable INPUT$ -- which a real C128 would
* refuse too, so the reference program is the wrong one. But the corpus is
* the acceptance contract and it lives in a submodule this repository may
* not edit, so the diagnostic and the corpus cannot both be right. TODO.md
* section 6 records the trade rather than making it silently.
*/
HARNESS_RUNTIME.hasError = false;
TEST_REQUIRE_OK(akbasic_scanner_scan(&HARNESS_RUNTIME, "PRINT$ = 1", NULL, 0));
TEST_REQUIRE(HARNESS_RUNTIME.hasError, "PRINT$ should be refused as a variable name");
/*
* 12.10 -- a leading zero selects base 8, so `PRINT 010` prints 8 and
* `PRINT 08` will not parse. Commodore BASIC has no octal literals.
*/
TEST_REQUIRE_OK(akbasic_environment_zero(HARNESS_RUNTIME.environment));
TEST_REQUIRE_OK(harness_parse("A# = 010", &leaf));
TEST_REQUIRE_OK(akbasic_runtime_evaluate(&HARNESS_RUNTIME, leaf, &out));
TEST_REQUIRE_INT(out->intval, 10);
harness_stop();
return akbasic_test_failures;
}