Accept GRAPHIC CLR and a negative DATA item

Two parse handlers refusing something the documentation promises. Landing
together because they are the same defect twice -- a verb's own argument shape
falling through to a general path that cannot see it -- in one file, found by
one program, and verified in one pass.

**`GRAPHIC CLR`** is given as `GRAPHIC mode | CLR` in both docs/06-graphics.md
and docs/11-verb-reference.md, and was refused: `CLR` is a verb of its own, so
the generic arglist path scanned it as a command token and the expression parser
answered "Expected expression or literal". `akbasic_parse_graphic()` takes it as
this verb's keyword argument and emits mode 5 -- which `akbasic_cmd_graphic()`
already treats as "drop the saved shapes and go back to text", so both spellings
are one statement and the exec handler is untouched. The documentation was right
all along; nothing in it changes.

**`DATA -5`** was refused by `akbasic_parse_data()`, and only there: `READ` scans
the source text directly (src/data.c) and always returned the -5 intact. So the
value was right and *reaching* the statement raised -- which, since section 4
settled that `DATA` at run time is a no-op, is what a program does with every
`DATA` line it walks past. A table of coordinates or velocities is full of
negative numbers, which is how a game found it.

The fix accepts a unary minus over a numeric literal and nothing else: `DATA -A#`
is still a mistake worth naming, and `akbasic_leaf_is_literal()` keeps meaning
what it says because other callers rely on it.

tests/read_data.c covers both mechanisms -- reading a negative item and reaching
the line after it -- with a mixed-sign table and a negative float, since the two
were never the same code path.

TODO.md section 9 items 7 and 8, struck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 00:18:53 -04:00
parent a1dcfcacf3
commit d5f3c3ef5b
6 changed files with 130 additions and 3 deletions

View File

@@ -57,6 +57,46 @@ akerr_ErrorContext *akbasic_parse_arglist(akbasic_Parser *parser, akbasic_ASTLea
SUCCEED_RETURN(errctx);
}
/*
* GRAPHIC mode [, clear]
* GRAPHIC CLR
*
* `CLR` is a keyword argument rather than an expression, and the generic arglist
* path cannot see it as one: `CLR` is a verb of its own, so it scans as a
* command token and the expression parser refuses with "Expected expression or
* literal". Both `docs/06-graphics.md` and `docs/11-verb-reference.md` have given
* the form as `GRAPHIC mode | CLR` all along, so the documentation was right and
* the parser was not. TODO.md section 9 item 7.
*
* It becomes mode 5, which is what akbasic_cmd_graphic() already treats as "drop
* the saved shapes and go back to text" -- so `GRAPHIC CLR` and `GRAPHIC 5` are
* the same statement, and the exec handler needs no change.
*/
akerr_ErrorContext *akbasic_parse_graphic(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
akbasic_ASTLeaf *arglist = NULL;
akbasic_ASTLeaf *expr = NULL;
akbasic_Token *peeked = NULL;
peeked = akbasic_parser_peek(parser);
if ( peeked != NULL && peeked->tokentype == AKBASIC_TOK_COMMAND &&
strcmp(peeked->lexeme, "CLR") == 0 ) {
(void)akbasic_parser_match1(parser, AKBASIC_TOK_COMMAND);
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist));
arglist->leaftype = AKBASIC_LEAF_ARGUMENTLIST;
arglist->operator_ = AKBASIC_TOK_FUNCTION_ARGUMENT;
PASS(errctx, akbasic_parser_new_leaf(parser, &arglist->right));
PASS(errctx, akbasic_leaf_new_literal_int(arglist->right, "5"));
PASS(errctx, akbasic_parser_new_leaf(parser, &expr));
PASS(errctx, akbasic_leaf_new_command(expr, "GRAPHIC", arglist));
*dest = expr;
SUCCEED_RETURN(errctx);
}
PASS(errctx, akbasic_parse_arglist(parser, dest));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_parse_draw(akbasic_Parser *parser, akbasic_ASTLeaf **dest)
{
PREPARE_ERROR(errctx);
@@ -942,6 +982,26 @@ akerr_ErrorContext *akbasic_parse_data(akbasic_Parser *parser, akbasic_ASTLeaf *
FAIL_ZERO_RETURN(errctx, (arglist != NULL && arglist->right != NULL), AKBASIC_ERR_SYNTAX,
"Expected literal");
for ( expr = arglist->right; expr != NULL; expr = expr->next ) {
/*
* A negated literal counts. `DATA -5` used to be refused here -- and only
* here: `READ` scans the source text directly (src/data.c) and returned
* the -5 intact, so the value was right and reaching the *statement*
* raised "Expected literal". Since section 4 settled that DATA at run
* time is a no-op, walking past one is exactly what a program does with
* it, and a table of coordinates or velocities is full of them.
* TODO.md section 9 item 8.
*
* Only over a numeric literal: `DATA -A#` is still a mistake worth
* naming, and akbasic_leaf_is_literal() keeps meaning what it says
* because it is used elsewhere.
*/
if ( expr->leaftype == AKBASIC_LEAF_UNARY &&
expr->operator_ == AKBASIC_TOK_MINUS &&
expr->left != NULL &&
(expr->left->leaftype == AKBASIC_LEAF_LITERAL_INT ||
expr->left->leaftype == AKBASIC_LEAF_LITERAL_FLOAT) ) {
continue;
}
FAIL_ZERO_RETURN(errctx, akbasic_leaf_is_literal(expr), AKBASIC_ERR_SYNTAX,
"Expected literal");
}

View File

@@ -83,7 +83,7 @@ static const akbasic_Verb VERBS[] = {
{ "GETKEY", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_getkey },
{ "GOSUB", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_gosub },
{ "GOTO", AKBASIC_TOK_COMMAND, -1, NULL, akbasic_cmd_goto },
{ "GRAPHIC", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_graphic },
{ "GRAPHIC", AKBASIC_TOK_COMMAND, -1, akbasic_parse_graphic, akbasic_cmd_graphic },
{ "GSHAPE", AKBASIC_TOK_COMMAND, -1, akbasic_parse_arglist, akbasic_cmd_gshape },
{ "HEADER", AKBASIC_TOK_COMMAND, -1, akbasic_parse_optional_arglist, akbasic_cmd_header },
{ "HELP", AKBASIC_TOK_COMMAND_IMMEDIATE, -1, NULL, akbasic_cmd_help },

View File

@@ -17,6 +17,7 @@
/* Parse handlers -- src/parser_commands.c */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_arglist(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_data(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_graphic(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_draw(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_def(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);
akerr_ErrorContext AKERR_NOIGNORE *akbasic_parse_dim(struct akbasic_Parser *parser, akbasic_ASTLeaf **dest);