Add records: TYPE, DIM ... AS, field access, copy on assign
BASIC 7.0 has no records at all, so none of this is a port. The `@` suffix was not invented either: the Go reference reserved IDENTIFIER_STRUCT and never used it, and src/grammar.c rendered such a leaf as "NOT IMPLEMENTED" until now. Declaring the type is what buys the storage model. A TYPE states its fields, so an instance has a known slot count and is laid out exactly as an array is -- one contiguous run from the same value pool DIM already draws from, with field access as offset arithmetic. No new pool holds data; the only new table holds descriptors. A nested value flattens into its container's run, which is why LINE with two POINTs and a string is five slots rather than three. Each field takes its type from its own suffix, the same rule every other name here follows, so a field list needs no type column. An `@` field is the exception and has to name its type, because three primitive types fit in three suffix characters and N declared types do not fit in one. The declaration is prescanned before the program runs, like labels and DATA and for the same reason: it has to be in effect wherever control goes. Three passes, each for a case the one before cannot do -- names first so a field can refer to a type declared later, then field lists, then sizes by repeated resolution. What never resolves is a cycle of by-value containment, so "a TYPE cannot contain itself by value" is a diagnosis rather than an assumption, and the message says to use PTR TO instead. Assignment copies. That interception is the whole feature and it cannot live in akbasic_value_clone(), which copies one slot -- and one slot holds a *reference* to an instance rather than the instance, so going through it would alias. A structure is intercepted before that path and its slots are copied one at a time, walking the descriptor rather than memcpy-ing the run, because a pointer field must copy its reference where a value field must copy its slots. Two smaller things the work required. All three prescans now sit inside one ATTEMPT: a malformed declaration is the program's mistake, and it was printing a stack trace and taking the driver with it, which is the boundary goal 3 exists to draw. And a fresh variable's structtype is -1 rather than the 0 a memset leaves, because 0 is a valid type index and every new variable was claiming to be the first type declared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,13 @@ akerr_ErrorContext *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_V
|
||||
if ( !obj->variables[i].used ) {
|
||||
memset(&obj->variables[i], 0, sizeof(obj->variables[i]));
|
||||
obj->variables[i].used = true;
|
||||
/*
|
||||
* Not zero: zero is a valid structure type index, so a memset alone
|
||||
* would hand back a fresh variable already claiming to be the first
|
||||
* TYPE the program declared -- and `DIM P@ AS RECT` would then refuse
|
||||
* it as a re-DIM of something never DIMmed at all.
|
||||
*/
|
||||
obj->variables[i].structtype = -1;
|
||||
*dest = &obj->variables[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -345,14 +352,53 @@ akerr_ErrorContext *akbasic_runtime_set_mode(akbasic_Runtime *obj, int mode)
|
||||
* argv takes, where the program does not exist yet when start() is called.
|
||||
*/
|
||||
if ( obj->mode == AKBASIC_MODE_RUN && obj->environment != NULL ) {
|
||||
PASS(errctx, akbasic_runtime_scan_labels(obj));
|
||||
/*
|
||||
* And the DATA items, for the same reason and at the same moment: READ
|
||||
* walks a cursor along a list built before the program runs, so a DATA
|
||||
* line *before* its READ is found -- which it was not when READ skipped
|
||||
* forward looking for one.
|
||||
* All three prescans, and all three inside one ATTEMPT.
|
||||
*
|
||||
* **A prescan failure is the program's mistake, not the host's**, so it
|
||||
* has to leave here as a BASIC error line rather than as a raised
|
||||
* context -- goal 3, the same boundary process_line_run() draws around
|
||||
* parsing. Without this a malformed TYPE printed a stack trace and took
|
||||
* the driver with it, which is exactly what section 8 records for the
|
||||
* scanner on a path this one would otherwise have joined.
|
||||
*
|
||||
* It matters most for TYPE because a mistyped declaration is an ordinary
|
||||
* thing to write; labels and DATA are wrapped with it because they can
|
||||
* fail too and there is no reason for three different answers.
|
||||
*/
|
||||
PASS(errctx, akbasic_data_scan(obj));
|
||||
ATTEMPT {
|
||||
/*
|
||||
* Labels first, filed here rather than in any one of the several
|
||||
* places that start a run. Every one of them --
|
||||
* akbasic_runtime_start(), RUN, CONT, and the end of a RUNSTREAM
|
||||
* load -- arrives through this function, and the last of those is
|
||||
* the one a driver reading a file from argv takes, where the program
|
||||
* does not exist yet when start() is called.
|
||||
*/
|
||||
CATCH(errctx, akbasic_runtime_scan_labels(obj));
|
||||
/*
|
||||
* Then the DATA items, for the same reason: READ walks a cursor
|
||||
* along a list built before the program runs, so a DATA line
|
||||
* *before* its READ is found -- which it was not when READ skipped
|
||||
* forward looking for one.
|
||||
*/
|
||||
CATCH(errctx, akbasic_data_scan(obj));
|
||||
/*
|
||||
* Then the TYPE declarations, for the third time and the same
|
||||
* reason: a declaration has to be in effect wherever control goes,
|
||||
* so `DIM P@ AS RECT` cannot run before RECT exists even if a branch
|
||||
* skipped the lines that declared it.
|
||||
*/
|
||||
CATCH(errctx, akbasic_structtype_scan(obj));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
snprintf(message, sizeof(message), "%s", errctx->message);
|
||||
obj->lasterrorstatus = errctx->status;
|
||||
IGNORE(akbasic_runtime_error(obj, AKBASIC_ERRCLASS_PARSE, message));
|
||||
IGNORE(akbasic_runtime_set_mode(obj, obj->run_finished_mode));
|
||||
} FINISH(errctx, false);
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -423,6 +469,26 @@ static akerr_ErrorContext *evaluate_identifier(akbasic_Runtime *obj, akbasic_AST
|
||||
"Identifier %s is undefined", expr->identifier);
|
||||
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &slot));
|
||||
|
||||
/*
|
||||
* A structure variable's *slots are* the instance, so what an expression
|
||||
* gets is a description of where it lives rather than a copy of it -- one
|
||||
* value cannot hold a run of them. Assignment is what turns that
|
||||
* description into a copy, which is the one place a copy was actually asked
|
||||
* for.
|
||||
*/
|
||||
if ( variable->valuetype == AKBASIC_TYPE_STRUCT && variable->structtype >= 0 ) {
|
||||
PASS(errctx, akbasic_environment_new_value(obj->environment, ©));
|
||||
PASS(errctx, akbasic_struct_describe(obj, variable->structtype, slot,
|
||||
variable->ispointer, copy));
|
||||
if ( variable->ispointer ) {
|
||||
/* A pointer variable holds its reference in its own slot. */
|
||||
copy->structtype = slot->structtype;
|
||||
copy->structbase = slot->structbase;
|
||||
}
|
||||
*dest = copy;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
if ( !obj->eval_clone_identifiers ) {
|
||||
*dest = slot;
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -568,6 +634,7 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
case AKBASIC_LEAF_IDENTIFIER_INT:
|
||||
case AKBASIC_LEAF_IDENTIFIER_FLOAT:
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRING:
|
||||
case AKBASIC_LEAF_IDENTIFIER_STRUCT:
|
||||
PASS(errctx, evaluate_identifier(obj, expr, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -624,6 +691,10 @@ akerr_ErrorContext *akbasic_runtime_evaluate(akbasic_Runtime *obj, akbasic_ASTLe
|
||||
PASS(errctx, verb->exec(obj, expr, lval, rval, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
case AKBASIC_LEAF_FIELD:
|
||||
PASS(errctx, akbasic_struct_evaluate_field(obj, expr, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
case AKBASIC_LEAF_BINARY:
|
||||
PASS(errctx, evaluate_binary(obj, expr, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
Reference in New Issue
Block a user