Consume the COLON token: a line can hold several statements
The token has existed since the scanner was written and nothing read it, so 10 PRINT A$ : REM ... was a parse error. Leading separators are consumed before each statement and an empty statement yields a NULL leaf rather than an error, so a trailing colon and a run of them are both legal. BASIC 7.0 scopes everything after THEN to the condition, which the reference had no opinion about because it never got here. The rule is not "skip when false": the rest of the line belongs to whichever arm was written last. A whole FOR/NEXT on one line still does not loop -- block skipping works by source line. Recorded in TODO.md as what group A has to fix first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -414,6 +414,24 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
PASS(errctx, report_and_reraise(obj, errctx));
|
||||
} FINISH(errctx, true);
|
||||
/*
|
||||
* Who owns the rest of the line?
|
||||
*
|
||||
* BASIC 7.0 scopes every statement after THEN to the condition, and the
|
||||
* parser only ever takes *one* statement for each arm -- the rest arrive
|
||||
* at the statement loop as ordinary top-level statements. So the branch
|
||||
* has to say whether that loop should run them.
|
||||
*
|
||||
* IF C THEN A : B C false -> B belongs to THEN, skip it
|
||||
* IF C THEN A ELSE B : D C true -> D belongs to ELSE, skip it
|
||||
*
|
||||
* which is not "skip when false": the remainder always belongs to
|
||||
* whichever arm was written last, so it is skipped exactly when that arm
|
||||
* is the one *not* taken. With an ELSE present the last arm is ELSE;
|
||||
* without one it is THEN.
|
||||
*/
|
||||
obj->skiprestofline = ((expr->right != NULL) == (rval->boolvalue == AKBASIC_TRUE));
|
||||
|
||||
if ( rval->boolvalue == AKBASIC_TRUE ) {
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, expr->left, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -683,8 +701,9 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
|
||||
obj->environment->lineno += obj->autoLineNumber;
|
||||
PASS(errctx, akbasic_scanner_scan(obj, obj->userline, scanned, sizeof(scanned)));
|
||||
PASS(errctx, akbasic_parser_init(&parser, obj));
|
||||
obj->skiprestofline = false;
|
||||
|
||||
while ( !akbasic_parser_is_at_end(&parser) ) {
|
||||
while ( !akbasic_parser_is_at_end(&parser) && !obj->skiprestofline ) {
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akbasic_parser_parse(&parser, &leaf));
|
||||
} CLEANUP {
|
||||
@@ -698,6 +717,11 @@ akerr_ErrorContext *akbasic_runtime_process_line_repl(akbasic_Runtime *obj)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
if ( leaf == NULL ) {
|
||||
/* Nothing but statement separators left; an empty statement is not one. */
|
||||
continue;
|
||||
}
|
||||
|
||||
PASS(errctx, akbasic_runtime_interpret_immediate(obj, leaf, &value));
|
||||
if ( value == NULL ) {
|
||||
/* Not an immediate command, so it is program text: file it. */
|
||||
@@ -732,8 +756,9 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
|
||||
|
||||
PASS(errctx, akbasic_scanner_scan(obj, line, NULL, 0));
|
||||
PASS(errctx, akbasic_parser_init(&parser, obj));
|
||||
obj->skiprestofline = false;
|
||||
|
||||
while ( !akbasic_parser_is_at_end(&parser) ) {
|
||||
while ( !akbasic_parser_is_at_end(&parser) && !obj->skiprestofline ) {
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akbasic_parser_parse(&parser, &leaf));
|
||||
} CLEANUP {
|
||||
@@ -747,6 +772,10 @@ akerr_ErrorContext *akbasic_runtime_process_line_run(akbasic_Runtime *obj)
|
||||
if ( obj->errclass != AKBASIC_ERRCLASS_NONE ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
if ( leaf == NULL ) {
|
||||
/* Nothing but statement separators left; an empty statement is not one. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* The reference discards both results here. An error has already been
|
||||
|
||||
Reference in New Issue
Block a user